Skip to content

Commit

Permalink
Added command to generate chain link JSON
Browse files Browse the repository at this point in the history
See PR #583
  • Loading branch information
dadamu authored Aug 14, 2021
1 parent b42ce83 commit cddb501
Show file tree
Hide file tree
Showing 11 changed files with 444 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Renamed `PollData` and `PollAnswer` to `Poll` and `ProvidedAnswer` ([\#536]((https://github.com/desmos-labs/desmos/issues/536)))
- Enabled snapshot by default ([\#529](https://github.com/desmos-labs/desmos/pull/529))
- Improved the performance of profile validation checks ([\#548](https://github.com/desmos-labs/desmos/pull/548))
- Added `create-chain-link-json` command ([\#572](https://github.com/desmos-labs/desmos/pull/572))


## Version 0.17.7
Expand Down
101 changes: 101 additions & 0 deletions app/desmos/cmd/chainlink/create_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package chainlink

import (
"encoding/hex"
"fmt"
"io/ioutil"

chainlinktypes "github.com/desmos-labs/desmos/app/desmos/cmd/chainlink/types"

"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/cobra"

"github.com/desmos-labs/desmos/app"
profilescliutils "github.com/desmos-labs/desmos/x/profiles/client/utils"
profilestypes "github.com/desmos-labs/desmos/x/profiles/types"
)

// GetCreateChainLinkJSON returns the command allowing to generate the chain link JSON
// file that is required by the link-chain command
func GetCreateChainLinkJSON() *cobra.Command {
return &cobra.Command{
Use: "create-chain-link-json",
Short: "Start an interactive prompt to create a new chain link JSON object",
Long: `Start an interactive prompt to create a new chain link JSON object that can be used to later link your Desmos profile to another chain.
Once you have built the JSON object using this command, you can then run the following command to complete the linkage:
desmos tx profiles link-chain [/path/to/json/file.json]
Note that this command will ask you the mnemonic that should be used to generate the private key of the address you want to link.
The mnemonic is only used temporarily and never stored anywhere.`,
RunE: func(cmd *cobra.Command, args []string) error {
// Build the chain link reference getter
getter := chainlinktypes.NewChainLinkReferencePrompt()

// Get the data
mnemonic, err := getter.GetMnemonic()
if err != nil {
return err
}

chain, err := getter.GetChain()
if err != nil {
return err
}

filename, err := getter.GetFilename()
if err != nil {
return err
}

// Build che chain link JSON
chainLinkJSON, err := generateChainLinkJSON(mnemonic, chain)
if err != nil {
return err
}

cdc, _ := app.MakeCodecs()
bz, err := cdc.MarshalJSON(&chainLinkJSON)
if err != nil {
return err
}

if filename != "" {
if err := ioutil.WriteFile(filename, bz, 0600); err != nil {
return err
}
}

cmd.Println(fmt.Sprintf("Chain link JSON file stored at %s", filename))

return nil
},
}
}

// generateChainLinkJSON returns build a new ChainLinkJSON intance using the provided mnemonic and chain configuration
func generateChainLinkJSON(mnemonic string, chain chainlinktypes.Chain) (profilescliutils.ChainLinkJSON, error) {
// Create an in-memory keybase for signing
keyBase := keyring.NewInMemory()
keyName := "chainlink"
_, err := keyBase.NewAccount(keyName, mnemonic, "", chain.DerivationPath, hd.Secp256k1)
if err != nil {
return profilescliutils.ChainLinkJSON{}, err
}

// Generate the proof signing it with the key
key, _ := keyBase.Key(keyName)
addr, _ := sdk.Bech32ifyAddressBytes(chain.Prefix, key.GetAddress())
sig, pubkey, err := keyBase.Sign(keyName, []byte(addr))
if err != nil {
return profilescliutils.ChainLinkJSON{}, err
}

return profilescliutils.NewChainLinkJSON(
profilestypes.NewBech32Address(addr, chain.Prefix),
profilestypes.NewProof(pubkey, hex.EncodeToString(sig), addr),
profilestypes.NewChainConfig(chain.Name),
), nil
}
83 changes: 83 additions & 0 deletions app/desmos/cmd/chainlink/create_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package chainlink_test

import (
"os"
"testing"

cmd "github.com/desmos-labs/desmos/app/desmos/cmd/chainlink"
"github.com/desmos-labs/desmos/app/desmos/cmd/chainlink/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

"github.com/desmos-labs/desmos/app"
profilescliutils "github.com/desmos-labs/desmos/x/profiles/client/utils"
profilestypes "github.com/desmos-labs/desmos/x/profiles/types"
)

// MockGetter represents a mock implementation of ChainLinkReferenceGetter
type MockGetter struct{}

// GetMnemonic implements ChainLinkReferenceGetter
func (mock MockGetter) GetMnemonic() (string, error) {
return "clip toilet stairs jaguar baby over mosquito capital speed mule adjust eye print voyage verify smart open crack imitate auto gauge museum planet rebel", nil
}

// GetChain implements ChainLinkReferenceGetter
func (mock MockGetter) GetChain() (types.Chain, error) {
return types.NewChain("Cosmos", "cosmos", "cosmos", "m/44'/118'/0'/0/0"), nil
}

// GetFilename implements ChainLinkReferenceGetter
func (mock MockGetter) GetFilename() (string, error) {
return "", nil
}

func TestGetCreateChainLinkJSON(t *testing.T) {
cfg := sdk.GetConfig()
app.SetupConfig(cfg)

output := os.Stdout
clientCtx := client.Context{}.
WithOutput(output)

out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd.GetCreateChainLinkJSON(MockGetter{}), []string{})
require.NoError(t, err)

cdc, _ := app.MakeCodecs()
var data profilescliutils.ChainLinkJSON
err = cdc.UnmarshalJSON(out.Bytes(), &data)
require.NoError(t, err)

// create a account to inmemory keybase
keyBase := keyring.NewInMemory()
keyName := "chainlink"
_, err = keyBase.NewAccount(
"chainlink",
"clip toilet stairs jaguar baby over mosquito capital speed mule adjust eye print voyage verify smart open crack imitate auto gauge museum planet rebel",
"",
"m/44'/118'/0'/0/0",
hd.Secp256k1,
)
require.NoError(t, err)

// get key from keybase
key, err := keyBase.Key(keyName)
require.NoError(t, err)

expected := profilescliutils.NewChainLinkJSON(
profilestypes.NewBech32Address("cosmos13j7p6faa9jr8ty6lvqv0prldprr6m5xenmafnt", "cosmos"),
profilestypes.NewProof(
key.GetPubKey(),
"c3bd014b2178d63d94b9c28e628bfcf56736de28f352841b0bb27d6fff2968d62c13a10aeddd1ebfe3b13f3f8e61f79a2c63ae6ff5cb78cb0d64e6b0a70fae57",
"cosmos13j7p6faa9jr8ty6lvqv0prldprr6m5xenmafnt"),
profilestypes.NewChainConfig("cosmos"),
)

require.Equal(t, expected, data)

}
19 changes: 19 additions & 0 deletions app/desmos/cmd/chainlink/types/chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package types

// Chain contains the data of a single chain
type Chain struct {
ID string
Name string
Prefix string
DerivationPath string
}

// NewChain returns a new Chain instance
func NewChain(id, name, prefix, derivationPath string) Chain {
return Chain{
id,
name,
prefix,
derivationPath,
}
}
19 changes: 19 additions & 0 deletions app/desmos/cmd/chainlink/types/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package types

// Config contains the data of the configuration
type Config struct {
Chains []Chain
}

// DefaultConfig returns the default config instance of the configuration
func DefaultConfig() Config {
return Config{
Chains: []Chain{
NewChain("Desmos", "desmos", "desmos", "m/44'/852'/0'/0/0"),
NewChain("Cosmos", "cosmos", "cosmos", "m/44'/118'/0'/0/0"),
NewChain("Akash", "akash", "akash", "m/44'/118'/0'/0/0"),
NewChain("Osmosis", "osmosis", "osmo", "m/44'/118'/0'/0/0"),
NewChain("Other", "", "", ""),
},
}
}
Loading

0 comments on commit cddb501

Please sign in to comment.