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(gnodev): add balances & keybase support #1938

Merged
merged 25 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8ace17c
feat(gnodev): add balances handler
gfanton Apr 13, 2024
9bb5de6
fix: cleanup
gfanton Apr 15, 2024
ca194ef
fix: cleanup and move gnodev into /cmd
gfanton Apr 16, 2024
5bd0b6f
fix: update docs
gfanton Apr 16, 2024
446fd2f
fix: balances errors
gfanton Apr 16, 2024
80e4f99
fix: lint and remove unused
gfanton Apr 17, 2024
93e4adc
chore: lint comment
gfanton Apr 17, 2024
ecc305f
fix: lint
gfanton Apr 18, 2024
690be02
chore: update genesis-creator to deploy-key
gfanton Apr 18, 2024
fb98ef5
chore: update creator to deployer
gfanton Apr 18, 2024
1012bec
chore: lint
gfanton Apr 18, 2024
b2ae172
chore: fix typo
gfanton Apr 22, 2024
9cfd3d7
fix: add balance file flag
gfanton Apr 23, 2024
230738f
chore: rename varAccounts into varPremineAccounts
gfanton Apr 23, 2024
87ec0b3
feat: add addressBook in gnodev to replace keybase
gfanton Apr 23, 2024
f2593b0
fix: lint error
gfanton Apr 23, 2024
0abe926
chore: move balance types in its own file
gfanton Apr 23, 2024
e4ac3c1
chore: add parallel to some tests
gfanton Apr 23, 2024
63442ff
Merge remote-tracking branch 'master' into feat/gnodev-keybase
gfanton Apr 23, 2024
5e248a3
chore: update doc
gfanton Apr 23, 2024
5acc0a2
Merge branch 'master' into feat/gnodev-keybase
gfanton Apr 23, 2024
2442f1e
chore: remove testify jaekwon deps from bad merge
gfanton Apr 23, 2024
766585c
fix: balances tests
gfanton Apr 23, 2024
1f52b90
chore: rename unlimitedFund -> premineBalance
gfanton Apr 23, 2024
55b863f
chore: improve book readability
gfanton Apr 24, 2024
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
11 changes: 8 additions & 3 deletions contribs/gnodev/Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../../)
GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)"

GOBUILD_FLAGS := -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)"
rundep := go run -modfile ../../misc/devdeps/go.mod
golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint

install:
go install $(GOBUILD_FLAGS) .
go install $(GOBUILD_FLAGS) ./cmd/gnodev

build:
go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gno
go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gnodev

lint:
$(golangci_lint) --config ../../.github/golangci.yml run ./...
152 changes: 152 additions & 0 deletions contribs/gnodev/cmd/gnodev/accounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package main

import (
"fmt"
"log/slog"
"os"
"strings"
"text/tabwriter"

"github.com/gnolang/gno/contribs/gnodev/pkg/dev"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
"github.com/gnolang/gno/tm2/pkg/crypto/bip39"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/std"
)

type varAccounts map[string]std.Coins // name or bech32 to coins.

func (va *varAccounts) Set(value string) error {
if *va == nil {
*va = map[string]std.Coins{}
}
accounts := *va

user, amount, found := strings.Cut(value, ":")
gfanton marked this conversation as resolved.
Show resolved Hide resolved
accounts[user] = nil
if !found {
return nil
}

coins, err := std.ParseCoins(amount)
if err != nil {
return fmt.Errorf("unable to parse coins from %q: %w", user, err)
}

// Add the parsed amount to the user.
accounts[user] = coins
return nil
}

func (va varAccounts) String() string {
accs := make([]string, 0, len(va))
for user, balance := range va {
accs = append(accs, fmt.Sprintf("%s(%s)", user, balance.String()))
}

return strings.Join(accs, ",")
}

func generateBalances(kb keys.Keybase, cfg *devCfg) (gnoland.Balances, error) {
bls := gnoland.NewBalances()
unlimitedFund := std.Coins{std.NewCoin("ugnot", 10e12)}
gfanton marked this conversation as resolved.
Show resolved Hide resolved

keys, err := kb.List()
if err != nil {
return nil, fmt.Errorf("unable to list keys from keybase: %w", err)
}

// Automatically set every key from keybase to unlimited fund.
for _, key := range keys {
address := key.GetAddress()

// Check if a predefined amount has been set for this key.
found := unlimitedFund
if preDefinedFound, ok := cfg.additionalAccounts[key.GetName()]; ok && preDefinedFound != nil {
found = preDefinedFound
} else if preDefinedFound, ok := cfg.additionalAccounts[address.String()]; ok && preDefinedFound != nil {
found = preDefinedFound
}

bls[address] = gnoland.Balance{Amount: found, Address: address}
}

if cfg.balancesFile == "" {
return bls, nil
}

file, err := os.Open(cfg.balancesFile)
if err != nil {
return nil, fmt.Errorf("unable to open balance file %q: %w", cfg.balancesFile, err)
}

blsFile, err := gnoland.GetBalancesFromSheet(file)
if err != nil {
return nil, fmt.Errorf("unable to read balances file %q: %w", cfg.balancesFile, err)
}

// Left merge keybase balance into loaded file balance.
blsFile.LeftMerge(bls)
return blsFile, nil
}

func logAccounts(logger *slog.Logger, kb keys.Keybase, _ *dev.Node) error {
keys, err := kb.List()
if err != nil {
return fmt.Errorf("unable to get keybase keys list: %w", err)
}

var tab strings.Builder
tabw := tabwriter.NewWriter(&tab, 0, 0, 2, ' ', tabwriter.TabIndent)

fmt.Fprintln(tabw, "KeyName\tAddress\tBalance") // Table header.
for _, key := range keys {
if key.GetName() == "" {
continue // Skip empty key name.
}

address := key.GetAddress()
qres, err := client.NewLocal().ABCIQuery("auth/accounts/"+address.String(), []byte{})
if err != nil {
return fmt.Errorf("unable to query account %q: %w", address.String(), err)
}

var qret struct{ BaseAccount std.BaseAccount }
if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil {
return fmt.Errorf("unable to unmarshal query response: %w", err)
}

// Insert row with name, address, and balance amount.
fmt.Fprintf(tabw, "%s\t%s\t%s\n", key.GetName(),
address.String(),
qret.BaseAccount.GetCoins().String())
}
// Flush table.
tabw.Flush()

headline := fmt.Sprintf("(%d) known accounts", len(keys))
logger.Info(headline, "table", tab.String())
return nil
}

// CreateAccount creates a new account with the given name and adds it to the keybase.
func createAccount(kb keys.Keybase, accountName string) (keys.Info, string, error) {
gfanton marked this conversation as resolved.
Show resolved Hide resolved
entropy, err := bip39.NewEntropy(256)
if err != nil {
return nil, "", fmt.Errorf("error creating entropy: %w", err)
}

mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
return nil, "", fmt.Errorf("error generating mnemonic: %w", err)
}

key, err := kb.CreateAccount(accountName, mnemonic, "", "", 0, 0)
if err != nil {
return nil, "", err
}

return key, mnemonic, nil
}
35 changes: 35 additions & 0 deletions contribs/gnodev/cmd/gnodev/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"io"
"log/slog"

"github.com/charmbracelet/lipgloss"
"github.com/gnolang/gno/contribs/gnodev/pkg/logger"
gnolog "github.com/gnolang/gno/gno.land/pkg/log"
"github.com/muesli/termenv"
)

func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger {
level := slog.LevelInfo
if cfg.verbose {
level = slog.LevelDebug
}

if cfg.serverMode {
zaplogger := logger.NewZapLogger(out, level)
return gnolog.ZapLoggerToSlog(zaplogger)
}

// Detect term color profile
colorProfile := termenv.DefaultOutput().Profile
clogger := logger.NewColumnLogger(out, level, colorProfile)

// Register well known group color with system colors
clogger.RegisterGroupColor(NodeLogName, lipgloss.Color("3"))
clogger.RegisterGroupColor(WebLogName, lipgloss.Color("4"))
clogger.RegisterGroupColor(KeyPressLogName, lipgloss.Color("5"))
clogger.RegisterGroupColor(EventServerLogName, lipgloss.Color("6"))

return slog.New(clogger)
}
Loading