Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add contribs/gnomigrate #3063

Merged
merged 2 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading