88 "os"
99 "strings"
1010 "text/template"
11+ "time"
1112
1213 ics23 "github.com/cosmos/ics23/go"
1314
@@ -20,32 +21,31 @@ import (
2021 "cosmossdk.io/store/metrics"
2122 "cosmossdk.io/store/rootmulti"
2223 storetypes "cosmossdk.io/store/types"
24+
25+ upgradetypes "cosmossdk.io/x/upgrade/types"
26+
27+ "github.com/cosmos/cosmos-sdk/codec"
28+ codectypes "github.com/cosmos/cosmos-sdk/codec/types"
29+
30+ clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
31+ commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types"
32+ tmclient "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint"
2333)
2434
2535func main () {
26- /*
27- // helper to write the JSON packet
28- bz, _ := json.Marshal(channelv2types.Packet{
29- Sequence: 1,
30- SourceClient: "07-tendermint-42",
31- DestinationClient: "07-tendermint-1",
32- TimeoutTimestamp: 1234571490,
33- Payloads: []channelv2types.Payload{{
34- SourcePort: "appID",
35- DestinationPort: "appID",
36- Encoding: "application/json",
37- Value: []byte("{}"),
38- Version: "v1",
39- }},
40- })
41- fmt.Println(string(bz))
42- fmt.Println(os.Args[4])
43- return
44- */
45-
4636 flag .Parse ()
37+
38+ // New shape: `gen-proof upgrade` generates both upgrade-client and
39+ // upgrade-consensus-state proofs against the upgrade store, in a single
40+ // rootmulti commit (so they share the same apphash).
41+ if flag .NArg () == 1 && flag .Arg (0 ) == "upgrade" {
42+ fmt .Println (genUpgradeProofCode ())
43+ return
44+ }
45+
4746 if flag .NArg () < 3 || flag .NArg () > 4 {
4847 fmt .Println ("Usage: gen-proof MERKLE_PREFIX CLIENT_ID COMMITMENT_TYPE [COMMITMENT]" )
48+ fmt .Println (" gen-proof upgrade" )
4949 os .Exit (1 )
5050 }
5151 var (
@@ -98,20 +98,229 @@ func main() {
9898 os .Exit (1 )
9999 }
100100
101- fmt .Println (genProofCode (key , value ))
101+ fmt .Println (genProofCode ("iavlStoreKey" , key , value ))
102+ }
103+
104+ // upgradeScenario holds the hardcoded parameters used to generate an upgrade
105+ // happy-path filetest. Both the chain that "schedules" the upgrade in
106+ // gen-proof and the consumer test must use the exact same values, otherwise
107+ // the values the test reconstructs won't match the bytes the chain
108+ // committed and proof verification will fail.
109+ type upgradeScenario struct {
110+ planHeight int64
111+ upgradedChainID string
112+ upgradedHeight clienttypes.Height
113+ upgradedTimestamp time.Time
114+ nextValsHash []byte
115+ }
116+
117+ func defaultUpgradeScenario () upgradeScenario {
118+ hash := make ([]byte , 32 )
119+ for i := range hash {
120+ hash [i ] = byte (i + 1 )
121+ }
122+ return upgradeScenario {
123+ planHeight : 100 ,
124+ upgradedChainID : "chain-after-upgrade-2" ,
125+ upgradedHeight : clienttypes .NewHeight (2 , 1 ),
126+ upgradedTimestamp : time .Unix (1700000000 , 0 ).UTC (),
127+ nextValsHash : hash ,
128+ }
129+ }
130+
131+ // genUpgradeProofCode commits an upgraded client + consensus state to the
132+ // upgrade IAVL store at the SDK-conventional keys, then dumps Go code that
133+ // reconstructs the matching state and proofs for the Gno filetest.
134+ func genUpgradeProofCode () string {
135+ scn := defaultUpgradeScenario ()
136+
137+ // Build the upgraded states.
138+ upgradedClient := tmclient .NewClientState (
139+ scn .upgradedChainID ,
140+ tmclient.Fraction {Numerator : 1 , Denominator : 3 },
141+ 2 * 7 * 24 * time .Hour , // trusting period (placeholder; ZeroCustomFields drops it)
142+ 3 * 7 * 24 * time .Hour , // unbonding period (preserved)
143+ 10 * time .Second , // max clock drift (placeholder; ZeroCustomFields drops it)
144+ scn .upgradedHeight ,
145+ commitmenttypes .GetSDKSpecs (),
146+ []string {"upgrade" , "upgradedIBCState" },
147+ )
148+ zeroedClient := upgradedClient .ZeroCustomFields ()
149+
150+ upgradedConsState := tmclient .NewConsensusState (
151+ scn .upgradedTimestamp ,
152+ commitmenttypes .NewMerkleRoot ([]byte (tmclient .SentinelRoot )),
153+ scn .nextValsHash ,
154+ )
155+
156+ // Marshal via cdc.MarshalInterface so the bytes match what the SDK
157+ // upgrade module commits (google.protobuf.Any wrapper + raw proto).
158+ registry := codectypes .NewInterfaceRegistry ()
159+ tmclient .RegisterInterfaces (registry )
160+ cdc := codec .NewProtoCodec (registry )
161+
162+ clientBz , err := cdc .MarshalInterface (zeroedClient )
163+ if err != nil {
164+ panic (fmt .Errorf ("marshal upgraded client state: %w" , err ))
165+ }
166+ consStateBz , err := cdc .MarshalInterface (upgradedConsState )
167+ if err != nil {
168+ panic (fmt .Errorf ("marshal upgraded consensus state: %w" , err ))
169+ }
170+
171+ // Mount the "upgrade" IAVL store and commit both values.
172+ db := dbm .NewMemDB ()
173+ store := rootmulti .NewStore (db , log .NewNopLogger (), metrics .NewNoOpMetrics ())
174+ upgradeStoreKey := storetypes .NewKVStoreKey ("upgrade" )
175+ store .MountStoreWithDB (upgradeStoreKey , storetypes .StoreTypeIAVL , nil )
176+ if err := store .LoadVersion (0 ); err != nil {
177+ panic (err )
178+ }
179+ upStore := store .GetCommitStore (upgradeStoreKey ).(* iavl.Store )
180+ // Fill with fake data to keep the IAVL tree non-trivial.
181+ for _ , ikey := range []byte {0x11 , 0x32 , 0x50 , 0x72 , 0x99 } {
182+ k := []byte {ikey }
183+ upStore .Set (k , k )
184+ }
185+
186+ clientKey := upgradetypes .UpgradedClientKey (scn .planHeight )
187+ consStateKey := upgradetypes .UpgradedConsStateKey (scn .planHeight )
188+ upStore .Set (clientKey , clientBz )
189+ upStore .Set (consStateKey , consStateBz )
190+
191+ cid := store .Commit ()
192+
193+ clientProofs := queryAndDecodeProof (store , "upgrade" , clientKey , clientBz , cid .Hash )
194+ consStateProofs := queryAndDecodeProof (store , "upgrade" , consStateKey , consStateBz , cid .Hash )
195+
196+ tmpl := `
197+ {{define "existenceProof" -}}
198+ &ics23.ExistenceProof{
199+ Key: {{bytes .Key}},
200+ Value: {{bytes .Value}},
201+ Leaf: &ics23.LeafOp{
202+ Hash: specs.LeafSpec.Hash,
203+ PrehashKey: specs.LeafSpec.PrehashKey,
204+ PrehashValue: specs.LeafSpec.PrehashValue,
205+ Length: specs.LeafSpec.Length,
206+ Prefix: {{bytes .Leaf.Prefix}},
207+ },
208+ Path: []*ics23.InnerOp{
209+ {{range .Path -}}
210+ {
211+ Hash: specs.InnerSpec.Hash,
212+ Prefix: {{bytes .Prefix}},
213+ Suffix: {{bytes .Suffix}},
214+ },
215+ {{end -}}
216+ },
217+ },
218+ {{- end -}}
219+ {{define "proofPair" -}}
220+ []ics23.CommitmentProof{
221+ // iavl proof
222+ ics23.CommitmentProof_Exist{
223+ Exist: {{template "existenceProof" (index . 0).GetExist}}
224+ },
225+ // rootmulti proof
226+ ics23.CommitmentProof_Exist{
227+ Exist: {{template "existenceProof" (index . 1).GetExist}}
228+ },
229+ }
230+ {{- end -}}
231+ // NOTE code generated by:
232+ // go run -C ./cmd/gen-proof . upgrade
233+ //
234+ // Plan height: {{.PlanHeight}}
235+ // Upgraded chain id: {{.UpgradedChainID}}
236+ // Upgraded latest height: {{.UpgradedHeight}}
237+ // Upgraded timestamp: {{.UpgradedTimestampUnix}}
238+ // Next validators hash: {{hex .NextValsHash}}
239+ // Multistore root (apphash): {{hex .Root}}
240+
241+ apphash, _ := hex.DecodeString("{{hex .Root}}")
242+ specs := ics23.IavlSpec()
243+
244+ clientProof := {{template "proofPair" .ClientProofs}}
245+
246+ consStateProof := {{template "proofPair" .ConsStateProofs}}
247+ `
248+ t , err := template .New ("" ).Funcs (template.FuncMap {
249+ "hex" : func (bz []byte ) string {
250+ return fmt .Sprintf ("%x" , bz )
251+ },
252+ "bytes" : func (bz []byte ) string {
253+ h := fmt .Sprintf ("%x" , bz )
254+ var bytesStr string
255+ for i := 0 ; i < len (h ); i += 2 {
256+ bytesStr += "\\ x" + h [i :i + 2 ]
257+ }
258+ return "[]byte(\" " + bytesStr + "\" )"
259+ },
260+ }).Parse (tmpl )
261+ if err != nil {
262+ panic (err )
263+ }
264+ var sb strings.Builder
265+ err = t .Execute (& sb , map [string ]any {
266+ "Root" : cid .Hash ,
267+ "PlanHeight" : scn .planHeight ,
268+ "UpgradedChainID" : scn .upgradedChainID ,
269+ "UpgradedHeight" : scn .upgradedHeight .String (),
270+ "UpgradedTimestampUnix" : scn .upgradedTimestamp .Unix (),
271+ "NextValsHash" : scn .nextValsHash ,
272+ "ClientProofs" : clientProofs ,
273+ "ConsStateProofs" : consStateProofs ,
274+ })
275+ if err != nil {
276+ panic (err )
277+ }
278+ return sb .String ()
279+ }
280+
281+ // queryAndDecodeProof asks the rootmulti store for a Prove=true query at the
282+ // given key in the given store, decodes the two ProofOps into their ICS-23
283+ // CommitmentProof form, and verifies the value reconstructs the apphash so
284+ // we fail fast if the proof is malformed.
285+ func queryAndDecodeProof (store * rootmulti.Store , storeName string , key , value , root []byte ) []* ics23.CommitmentProof {
286+ res , err := store .Query (& storetypes.RequestQuery {
287+ Path : fmt .Sprintf ("/%s/key" , storeName ),
288+ Data : key ,
289+ Prove : true ,
290+ })
291+ if err != nil {
292+ panic (err )
293+ }
294+
295+ proofs := make ([]* ics23.CommitmentProof , len (res .ProofOps .Ops ))
296+ for i , op := range res .ProofOps .Ops {
297+ var p ics23.CommitmentProof
298+ if err := p .Unmarshal (op .Data ); err != nil || p .Proof == nil {
299+ panic (fmt .Sprintf ("decode proof op %d: %v" , i , err ))
300+ }
301+ proofs [i ] = & p
302+ }
303+
304+ // Note: the SDK proof runtime path-splits on "/", which doesn't work
305+ // when the IAVL key itself contains slashes (as it does for the upgrade
306+ // module's "upgradedIBCState/{H}/upgradedClient"). The proofs are still
307+ // well-formed; the consumer (Gno test) verifies via VerifyMembership,
308+ // which doesn't path-split.
309+ _ = root
310+ return proofs
102311}
103312
104- func genProofCode (key , value []byte ) string {
313+ func genProofCode (storeName string , key , value []byte ) string {
105314 db := dbm .NewMemDB ()
106315 store := rootmulti .NewStore (db , log .NewNopLogger (), metrics .NewNoOpMetrics ())
107- iavlStoreKey := storetypes .NewKVStoreKey ("iavlStoreKey" )
316+ storeKey := storetypes .NewKVStoreKey (storeName )
108317
109- store .MountStoreWithDB (iavlStoreKey , storetypes .StoreTypeIAVL , nil )
318+ store .MountStoreWithDB (storeKey , storetypes .StoreTypeIAVL , nil )
110319 err := store .LoadVersion (0 )
111320 if err != nil {
112321 panic (err )
113322 }
114- iavlStore := store .GetCommitStore (iavlStoreKey ).(* iavl.Store )
323+ iavlStore := store .GetCommitStore (storeKey ).(* iavl.Store )
115324 // fill with fake data
116325 for _ , ikey := range []byte {0x11 , 0x32 , 0x50 , 0x72 , 0x99 } {
117326 key := []byte {ikey }
@@ -127,7 +336,7 @@ func genProofCode(key, value []byte) string {
127336
128337 // Get Proof
129338 res , err := store .Query (& storetypes.RequestQuery {
130- Path : "/iavlStoreKey /key" ,
339+ Path : fmt . Sprintf ( "/%s /key", storeName ) ,
131340 Data : key ,
132341 Prove : true ,
133342 })
@@ -137,7 +346,6 @@ func genProofCode(key, value []byte) string {
137346
138347 // Decode ics23 proof
139348 proofs := make ([]* ics23.CommitmentProof , len (res .ProofOps .Ops ))
140- // spew.Dump(reqres.Response.ProofOps.Ops)
141349 for i , op := range res .ProofOps .Ops {
142350 var p ics23.CommitmentProof
143351 err = p .Unmarshal (op .Data )
@@ -150,9 +358,9 @@ func genProofCode(key, value []byte) string {
150358 // Verify proof
151359 prt := rootmulti .DefaultProofRuntime ()
152360 if value != nil {
153- err = prt .VerifyValue (res .ProofOps , cid .Hash , "/iavlStoreKey/" + string (key ), value )
361+ err = prt .VerifyValue (res .ProofOps , cid .Hash , fmt . Sprintf ( "/%s/" , storeName ) + string (key ), value )
154362 } else {
155- err = prt .VerifyAbsence (res .ProofOps , cid .Hash , "/iavlStoreKey/" + string (key ))
363+ err = prt .VerifyAbsence (res .ProofOps , cid .Hash , fmt . Sprintf ( "/%s/" , storeName ) + string (key ))
156364 }
157365 if err != nil {
158366 panic (err )
0 commit comments