Skip to content

Commit

Permalink
Merge pull request #694 from nspcc-dev/feature/getclaimable
Browse files Browse the repository at this point in the history
rpc, cli: support gas claim and asset transfer
  • Loading branch information
roman-khimov committed Mar 2, 2020
2 parents 252a9f2 + 1264b62 commit 657f5e4
Show file tree
Hide file tree
Showing 25 changed files with 773 additions and 62 deletions.
211 changes: 211 additions & 0 deletions cli/wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ package wallet

import (
"bufio"
"context"
"errors"
"fmt"
"os"
"strings"
"syscall"

"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/rpc/client"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/wallet"
"github.com/urfave/cli"
"golang.org/x/crypto/ssh/terminal"
Expand All @@ -33,6 +39,14 @@ var (
Name: "decrypt, d",
Usage: "Decrypt encrypted keys.",
}
rpcFlag = cli.StringFlag{
Name: "rpc, r",
Usage: "RPC node address",
}
timeoutFlag = cli.DurationFlag{
Name: "timeout, t",
Usage: "Timeout for the operation",
}
)

// NewCommands returns 'wallet' command.
Expand All @@ -41,6 +55,20 @@ func NewCommands() []cli.Command {
Name: "wallet",
Usage: "create, open and manage a NEO wallet",
Subcommands: []cli.Command{
{
Name: "claim",
Usage: "claim GAS",
Action: claimGas,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
timeoutFlag,
cli.StringFlag{
Name: "address, a",
Usage: "Address to claim GAS for",
},
},
},
{
Name: "create",
Usage: "create a new wallet",
Expand Down Expand Up @@ -112,10 +140,106 @@ func NewCommands() []cli.Command {
},
},
},
{
Name: "transfer",
Usage: "transfer NEO/GAS",
UsageText: "transfer --path <path> --from <addr> --to <addr>" +
" --amount <amount> --asset [NEO|GAS|<hex-id>]",
Action: transferAsset,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
timeoutFlag,
cli.StringFlag{
Name: "from",
Usage: "Address to send an asset from",
},
cli.StringFlag{
Name: "to",
Usage: "Address to send an asset to",
},
cli.StringFlag{
Name: "amount",
Usage: "Amount of asset to send",
},
cli.StringFlag{
Name: "asset",
Usage: "Asset ID",
},
},
},
},
}}
}

func claimGas(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("path"))
if err != nil {
return cli.NewExitError(err, 1)
}
defer wall.Close()

addr := ctx.String("address")
scriptHash, err := address.StringToUint160(addr)
if err != nil {
return cli.NewExitError(err, 1)
}

acc := wall.GetAccount(scriptHash)
if acc == nil {
return cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", addr), 1)
}

pass, err := readPassword("Enter password > ")
if err != nil {
return cli.NewExitError(err, 1)
} else if err := acc.Decrypt(pass); err != nil {
return cli.NewExitError(err, 1)
}

gctx, cancel := getGoContext(ctx)
defer cancel()

c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
if err != nil {
return cli.NewExitError(err, 1)
}
info, err := c.GetClaimable(addr)
if err != nil {
return cli.NewExitError(err, 1)
} else if info.Unclaimed == 0 || len(info.Spents) == 0 {
fmt.Println("Nothing to claim")
return nil
}

var claim transaction.ClaimTX
for i := range info.Spents {
claim.Claims = append(claim.Claims, transaction.Input{
PrevHash: info.Spents[i].Tx,
PrevIndex: uint16(info.Spents[i].N),
})
}

tx := &transaction.Transaction{
Type: transaction.ClaimType,
Data: &claim,
}

tx.AddOutput(&transaction.Output{
AssetID: core.UtilityTokenID(),
Amount: info.Unclaimed,
ScriptHash: scriptHash,
})

_ = acc.SignTx(tx)
if err := c.SendRawTransaction(tx); err != nil {
return cli.NewExitError(err, 1)
}

fmt.Println(tx.Hash().StringLE())
return nil
}

func addAccount(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("path"))
if err != nil {
Expand Down Expand Up @@ -249,6 +373,81 @@ func importWallet(ctx *cli.Context) error {
return nil
}

func transferAsset(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("path"))
if err != nil {
return cli.NewExitError(err, 1)
}
defer wall.Close()

from := ctx.String("from")
addr, err := address.StringToUint160(from)
if err != nil {
return cli.NewExitError("invalid address", 1)
}
acc := wall.GetAccount(addr)
if acc == nil {
return cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", addr), 1)
}

asset, err := getAssetID(ctx.String("asset"))
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid asset id: %v", err), 1)
}

amount, err := util.Fixed8FromString(ctx.String("amount"))
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1)
}

pass, err := readPassword("Enter wallet password > ")
if err != nil {
return cli.NewExitError(err, 1)
} else if err := acc.Decrypt(pass); err != nil {
return cli.NewExitError(err, 1)
}

gctx, cancel := getGoContext(ctx)
defer cancel()

c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
if err != nil {
return cli.NewExitError(err, 1)
}

tx := transaction.NewContractTX()
tx.Data = new(transaction.ContractTX)
if err := request.AddInputsAndUnspentsToTx(tx, from, asset, amount, c); err != nil {
return cli.NewExitError(err, 1)
}

toAddr, err := address.StringToUint160(ctx.String("to"))
if err != nil {
return cli.NewExitError(err, 1)
}
tx.AddOutput(&transaction.Output{
AssetID: asset,
Amount: amount,
ScriptHash: toAddr,
Position: 1,
})

_ = acc.SignTx(tx)
if err := c.SendRawTransaction(tx); err != nil {
return cli.NewExitError(err, 1)
}

fmt.Println(tx.Hash().StringLE())
return nil
}

func getGoContext(ctx *cli.Context) (context.Context, func()) {
if dur := ctx.Duration("timeout"); dur != 0 {
return context.WithTimeout(context.Background(), dur)
}
return context.Background(), func() {}
}

func dumpWallet(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("path"))
if err != nil {
Expand Down Expand Up @@ -331,6 +530,18 @@ func openWallet(path string) (*wallet.Wallet, error) {
return wallet.NewWalletFromFile(path)
}

func getAssetID(s string) (util.Uint256, error) {
s = strings.ToLower(s)
switch {
case s == "neo":
return core.GoverningTokenID(), nil
case s == "gas":
return core.UtilityTokenID(), nil
default:
return util.Uint256DecodeStringLE(s)
}
}

func newAccountFromWIF(wif string) (*wallet.Account, error) {
// note: NEP2 strings always have length of 58 even though
// base58 strings can have different lengths even if slice lengths are equal
Expand Down
6 changes: 4 additions & 2 deletions integration/performance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/network"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/wallet"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
Expand Down Expand Up @@ -49,10 +49,12 @@ func prepareData(t *testing.B) []*transaction.Transaction {
var data []*transaction.Transaction

wif := getWif(t)
acc, err := wallet.NewAccountFromWIF(wif.S)
require.NoError(t, err)

for n := 0; n < t.N; n++ {
tx := getTX(t, wif)
require.NoError(t, request.SignTx(tx, wif))
require.NoError(t, acc.SignTx(tx))
data = append(data, tx)
}
return data
Expand Down
13 changes: 10 additions & 3 deletions pkg/consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,16 +476,23 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
}

func (s *service) getValidators(txx ...block.Transaction) []crypto.PublicKey {
var pKeys []*keys.PublicKey
var (
pKeys []*keys.PublicKey
err error
)
if len(txx) == 0 {
pKeys, _ = s.Chain.GetValidators()
pKeys, err = s.Chain.GetValidators()
} else {
ntxx := make([]*transaction.Transaction, len(txx))
for i := range ntxx {
ntxx[i] = txx[i].(*transaction.Transaction)
}

pKeys, _ = s.Chain.GetValidators(ntxx...)
pKeys, err = s.Chain.GetValidators(ntxx...)
}

if err != nil {
s.log.Error("error while trying to get validators", zap.Error(err))
}

pubs := make([]crypto.PublicKey, len(pKeys))
Expand Down
Loading

0 comments on commit 657f5e4

Please sign in to comment.