Skip to content

Commit

Permalink
feat: add contribs/gnomigrate (#3063)
Browse files Browse the repository at this point in the history
## Description

This PR introduces a small tool called `gnomigrate`, which will serve as
a helper for migrating Gno data.
Currently, it only supports transaction sheet migration from `std.Tx` to
`gnoland.TxWithMetadata`.

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
</details>
  • Loading branch information
zivkovicmilos authored Nov 4, 2024
1 parent c776e32 commit 879041f
Show file tree
Hide file tree
Showing 8 changed files with 758 additions and 0 deletions.
18 changes: 18 additions & 0 deletions contribs/gnomigrate/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
rundep := go run -modfile ../../misc/devdeps/go.mod
golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint


.PHONY: install
install:
go install .

.PHONY: build
build:
go build -o build/gnomigrate .

lint:
$(golangci_lint) --config ../../.github/golangci.yml run ./...

test:
go test $(GOTEST_FLAGS) -v ./...

59 changes: 59 additions & 0 deletions contribs/gnomigrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Overview

`gnomigrate` is a CLI tool designed to migrate Gno legacy data formats to the new standard formats used in Gno
blockchain.

## Features

- **Transaction Migration**: Converts legacy `std.Tx` transactions to the new `gnoland.TxWithMetadata` format.

## Installation

### Clone the repository

```shell
git clone https://github.com/gnolang/gno.git
```

### Navigate to the project directory

```shell
cd contribs/gnomigrate
```

### Build the binary

```shell
make build
```

### Install the binary

```shell
make install
```

## Migrating legacy transactions

The `gnomigrate` tool provides the `txs` subcommand to manage the migration of legacy transaction sheets.

```shell
gnomigrate txs --input-dir <input_directory> --output-dir <output_directory>
```

### Flags

- `--input-dir`: Specifies the directory containing the legacy transaction sheets to migrate.
- `--output-dir`: Specifies the directory where the migrated transaction sheets will be saved.

### Example

```shell
gnomigrate txs --input-dir ./legacy_txs --output-dir ./migrated_txs
```

This command will:

- Read all `.jsonl` files from the ./legacy_txs directory, that are Amino-JSON encoded `std.Tx`s.
- Migrate each transaction from `std.Tx` to `gnoland.TxWithMetadata` (no metadata).
- Save the migrated transactions to the `./migrated_txs` directory, preserving the original directory structure.
57 changes: 57 additions & 0 deletions contribs/gnomigrate/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module github.com/gnolang/gnomigrate

go 1.23

require (
github.com/gnolang/gno v0.0.0-00010101000000-000000000000
github.com/stretchr/testify v1.9.0
)

replace github.com/gnolang/gno => ../..

require (
dario.cat/mergo v1.0.1 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
github.com/btcsuite/btcd/btcutil v1.1.6 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/peterbourgon/ff/v3 v3.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
go.etcd.io/bbolt v1.3.11 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/sdk v1.29.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
230 changes: 230 additions & 0 deletions contribs/gnomigrate/go.sum

Large diffs are not rendered by default.

199 changes: 199 additions & 0 deletions contribs/gnomigrate/internal/txs/txs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package txs

import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/std"
)

var (
errInvalidInputDir = errors.New("invalid input directory")
errInvalidOutputDir = errors.New("invalid output directory")
)

type txsCfg struct {
inputDir string
outputDir string
}

// NewTxsCmd creates the migrate txs subcommand
func NewTxsCmd(io commands.IO) *commands.Command {
cfg := &txsCfg{}

cmd := commands.NewCommand(
commands.Metadata{
Name: "txs",
ShortUsage: "<subcommand> [flags]",
ShortHelp: "manages the legacy transaction sheet migrations",
LongHelp: "Manages legacy transaction migrations through sheet input files",
},
cfg,
func(ctx context.Context, _ []string) error {
return cfg.execMigrate(ctx, io)
},
)

return cmd
}

func (c *txsCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(
&c.inputDir,
"input-dir",
"",
"the input directory for the legacy transaction sheets",
)

fs.StringVar(
&c.outputDir,
"output-dir",
"",
"the output directory for the standard transaction sheets",
)
}

func (c *txsCfg) execMigrate(ctx context.Context, io commands.IO) error {
// Make sure the dirs are set
if c.inputDir == "" {
return errInvalidInputDir
}

if c.outputDir == "" {
return errInvalidOutputDir
}

// Make sure the output dir is present
if err := os.MkdirAll(c.outputDir, os.ModePerm); err != nil {
return fmt.Errorf("unable to create output dir, %w", err)
}

return migrateDir(ctx, io, c.inputDir, c.outputDir)
}

// migrateDir migrates the transaction sheet directory
func migrateDir(
ctx context.Context,
io commands.IO,
sourceDir string,
outputDir string,
) error {
// Read the sheet directory
entries, err := os.ReadDir(sourceDir)
if err != nil {
return fmt.Errorf("error reading directory %s, %w", sourceDir, err)
}

for _, entry := range entries {
select {
case <-ctx.Done():
return nil
default:
var (
srcPath = filepath.Join(sourceDir, entry.Name())
destPath = filepath.Join(outputDir, entry.Name())
)

// Check if a dir is encountered
if !entry.IsDir() {
// Make sure the file type is valid
if !strings.HasSuffix(entry.Name(), ".jsonl") {
continue
}

// Process the tx sheet
io.Printfln("Migrating %s -> %s", srcPath, destPath)

if err := processFile(ctx, io, srcPath, destPath); err != nil {
io.ErrPrintfln("unable to process file %s, %w", srcPath, err)
}

continue
}

// Ensure destination directory exists
if err = os.MkdirAll(destPath, os.ModePerm); err != nil {
return fmt.Errorf("error creating directory %s, %w", destPath, err)
}

// Recursively process the directory
if err = migrateDir(ctx, io, srcPath, destPath); err != nil {
io.ErrPrintfln("unable migrate directory %s, %w", srcPath, err)
}
}
}

return nil
}

// processFile processes the old legacy std.Tx sheet into the new standard gnoland.TxWithMetadata
func processFile(ctx context.Context, io commands.IO, source, destination string) error {
file, err := os.Open(source)
if err != nil {
return fmt.Errorf("unable to open file, %w", err)
}
defer file.Close()

// Create the destination file
outputFile, err := os.Create(destination)
if err != nil {
return fmt.Errorf("unable to create file, %w", err)
}
defer outputFile.Close()

scanner := bufio.NewScanner(file)

scanner.Buffer(make([]byte, 1_000_000), 2_000_000)

for scanner.Scan() {
select {
case <-ctx.Done():
return nil
default:
var (
tx std.Tx
txWithMetadata gnoland.TxWithMetadata
)

if err = amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil {
io.ErrPrintfln("unable to read line, %s", err)

continue
}

// Convert the std.Tx -> gnoland.TxWithMetadata
txWithMetadata = gnoland.TxWithMetadata{
Tx: tx,
Metadata: nil, // not set
}

// Save the new transaction with metadata
marshaledData, err := amino.MarshalJSON(txWithMetadata)
if err != nil {
io.ErrPrintfln("unable to marshal tx, %s", err)

continue
}

if _, err = outputFile.WriteString(fmt.Sprintf("%s\n", string(marshaledData))); err != nil {
io.ErrPrintfln("unable to save to output file, %s", err)
}
}
}

// Check if there were any scanner errors
if err := scanner.Err(); err != nil {
return fmt.Errorf("error encountered during scan, %w", err)
}

return nil
}
Loading

0 comments on commit 879041f

Please sign in to comment.