-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
contribs/gnomigrate
(#3063)
## 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
1 parent
c776e32
commit 879041f
Showing
8 changed files
with
758 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ./... | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.