Skip to content

Commit c2f0fb7

Browse files
docs(gnogenesis): add documentation for genesis_txs.jsonl file (#5024)
This PR is made since in the past i created a tool for samourai zenao project to generate txs file from a SQL database state and struggled to find informations about how to generate a genesis_txs.jsonl from scratch. I thought it was a "unique" case but then a student from the student programs asked how to do the same. I explained him, but i thought it would be nice to add some documentation on the format and how to create a genesis_txs.jsonl file. My generator of genesis_txs file for zenao: https://github.com/samouraiworld/zenao/blob/03b5ef5f39e2a2cf987c4eacea6b6f3cd10a0b2a/backend/gzchain/gentxs.go --------- Co-authored-by: David <60177543+Davphla@users.noreply.github.com>
1 parent 929bc63 commit c2f0fb7

File tree

2 files changed

+202
-1
lines changed

2 files changed

+202
-1
lines changed

contribs/gnogenesis/README.md

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ The format for transactions in the transaction sheet is the following:
141141
To add genesis transactions from a file:
142142

143143
```shell
144-
gnogenesis txs add sheets ./txs.json
144+
gnogenesis txs add sheets ./txs.jsonl
145145
```
146146

147147
This outputs the initial transaction count.
@@ -183,3 +183,132 @@ The steps to get this sort of hash are:
183183
- cast the result to `types.Tx` (`bft`)
184184
- call `Hash` on the `types.Tx`
185185
- encode the result into base64
186+
187+
## Genesis Transaction Sheet Format Reference
188+
189+
This section provides a comprehensive reference for the `genesis_txs.jsonl` file format for genesis transactions.
190+
191+
- **Format**: JSONL (JSON Lines) - one transaction per line
192+
- **Encoding**: Amino JSON serialization
193+
194+
### Transaction Structure
195+
196+
Each line contains a `TxWithMetadata` object serialized as compact JSON (no newlines within the object):
197+
198+
```text
199+
{"tx":{"msg":[...],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[...],"memo":""},"metadata":{"timestamp":1234567890}}
200+
{"tx":{"msg":[...],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[...],"memo":""},"metadata":{"timestamp":1234567891}}
201+
```
202+
203+
The structure of each `TxWithMetadata` object (shown here formatted for readability):
204+
205+
```json
206+
{
207+
"tx": {
208+
"msg": [...],
209+
"fee": {
210+
"gas_wanted": "2000000",
211+
"gas_fee": "1000000ugnot"
212+
},
213+
"signatures": [...],
214+
"memo": ""
215+
},
216+
"metadata": {
217+
"timestamp": 1234567890
218+
}
219+
}
220+
```
221+
222+
### Message Types
223+
224+
Genesis transactions support three VM message types defined in [`gno.land/pkg/sdk/vm/msgs.go`](../../gno.land/pkg/sdk/vm/msgs.go):
225+
226+
| Type | Amino Type | Description |
227+
|------|------------|-------------|
228+
| `MsgCall` | `/vm.m_call` | Calls a function on an existing realm |
229+
| `MsgAddPackage` | `/vm.m_addpkg` | Deploys a new package or realm |
230+
| `MsgRun` | `/vm.m_run` | Executes ephemeral code (less common in genesis) |
231+
232+
### Transaction Ordering
233+
234+
Transactions **must be sorted by timestamp before writing** to ensure deterministic ordering when replaying genesis state. The sorting is not done automatically when reading, you must sort manually using `slices.SortStableFunc` as shown in the example below.
235+
236+
### Creating and writing a genesis_txs.jsonl file
237+
238+
The following example shows how to create transactions, sort them by timestamp, sign them, and write to a JSONL file:
239+
240+
[embedmd]:# (./_assets/genesis_txs_example.go go)
241+
```go
242+
package example
243+
244+
import (
245+
"cmp"
246+
"fmt"
247+
"os"
248+
"slices"
249+
"time"
250+
251+
"github.com/gnolang/gno/gno.land/pkg/gnoland"
252+
"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
253+
"github.com/gnolang/gno/tm2/pkg/amino"
254+
"github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
255+
"github.com/gnolang/gno/tm2/pkg/std"
256+
)
257+
258+
func createGenesisTxsFile(outputPath string, privKey secp256k1.PrivKeySecp256k1, chainID string) error {
259+
var txs []gnoland.TxWithMetadata
260+
261+
// Create a MsgCall transaction
262+
caller := privKey.PubKey().Address()
263+
callTx := gnoland.TxWithMetadata{
264+
Tx: std.Tx{
265+
Msgs: []std.Msg{
266+
vm.MsgCall{
267+
Caller: caller,
268+
PkgPath: "gno.land/r/demo/users",
269+
Func: "Register",
270+
Args: []string{"myusername"},
271+
Send: std.Coins{},
272+
},
273+
},
274+
Fee: std.NewFee(2000000, std.MustParseCoin("1000000ugnot")),
275+
},
276+
Metadata: &gnoland.GnoTxMetadata{
277+
Timestamp: time.Now().Unix(),
278+
},
279+
}
280+
txs = append(txs, callTx)
281+
282+
// Sort transactions by timestamp (required for deterministic ordering)
283+
slices.SortStableFunc(txs, func(a, b gnoland.TxWithMetadata) int {
284+
if a.Metadata == nil || b.Metadata == nil {
285+
return 0
286+
}
287+
return cmp.Compare(a.Metadata.Timestamp, b.Metadata.Timestamp)
288+
})
289+
290+
// Sign transactions
291+
if err := gnoland.SignGenesisTxs(txs, privKey, chainID); err != nil {
292+
return err
293+
}
294+
295+
// Write transactions to JSONL file
296+
file, err := os.Create(outputPath)
297+
if err != nil {
298+
return err
299+
}
300+
defer file.Close()
301+
302+
for _, tx := range txs {
303+
encoded, err := amino.MarshalJSON(tx)
304+
if err != nil {
305+
return err
306+
}
307+
if _, err := fmt.Fprintf(file, "%s\n", encoded); err != nil {
308+
return err
309+
}
310+
}
311+
312+
return nil
313+
}
314+
```
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package example
2+
3+
import (
4+
"cmp"
5+
"fmt"
6+
"os"
7+
"slices"
8+
"time"
9+
10+
"github.com/gnolang/gno/gno.land/pkg/gnoland"
11+
"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
12+
"github.com/gnolang/gno/tm2/pkg/amino"
13+
"github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
14+
"github.com/gnolang/gno/tm2/pkg/std"
15+
)
16+
17+
func createGenesisTxsFile(outputPath string, privKey secp256k1.PrivKeySecp256k1, chainID string) error {
18+
var txs []gnoland.TxWithMetadata
19+
20+
// Create a MsgCall transaction
21+
caller := privKey.PubKey().Address()
22+
callTx := gnoland.TxWithMetadata{
23+
Tx: std.Tx{
24+
Msgs: []std.Msg{
25+
vm.MsgCall{
26+
Caller: caller,
27+
PkgPath: "gno.land/r/demo/users",
28+
Func: "Register",
29+
Args: []string{"myusername"},
30+
Send: std.Coins{},
31+
},
32+
},
33+
Fee: std.NewFee(2000000, std.MustParseCoin("1000000ugnot")),
34+
},
35+
Metadata: &gnoland.GnoTxMetadata{
36+
Timestamp: time.Now().Unix(),
37+
},
38+
}
39+
txs = append(txs, callTx)
40+
41+
// Sort transactions by timestamp (required for deterministic ordering)
42+
slices.SortStableFunc(txs, func(a, b gnoland.TxWithMetadata) int {
43+
if a.Metadata == nil || b.Metadata == nil {
44+
return 0
45+
}
46+
return cmp.Compare(a.Metadata.Timestamp, b.Metadata.Timestamp)
47+
})
48+
49+
// Sign transactions
50+
if err := gnoland.SignGenesisTxs(txs, privKey, chainID); err != nil {
51+
return err
52+
}
53+
54+
// Write transactions to JSONL file
55+
file, err := os.Create(outputPath)
56+
if err != nil {
57+
return err
58+
}
59+
defer file.Close()
60+
61+
for _, tx := range txs {
62+
encoded, err := amino.MarshalJSON(tx)
63+
if err != nil {
64+
return err
65+
}
66+
if _, err := fmt.Fprintf(file, "%s\n", encoded); err != nil {
67+
return err
68+
}
69+
}
70+
71+
return nil
72+
}

0 commit comments

Comments
 (0)