Skip to content

Commit

Permalink
[BCI-2953] Initial Aptos keystore (#13564)
Browse files Browse the repository at this point in the history
* chain type to include aptos

* aptos key ed25519 implementation

* add aptos to keystore master

* added exports for keys

* removed sdk dependency

* updated go mod

* Added tests for account sdk deps

* added aptos keystore

* added interface assertion to solana

* added aptos keyring

* added aptos keystore mocks

* added aptos key controller & router

* Added aptos keys command

* added aptos keystore e2e tests

* Added Aptos network ORM test for key

* Added aptos ocr2 keys support

* Revert "Added Aptos network ORM test for key"

This reverts commit 444fe95.

* Added Aptos export functionality

* Modified Aptos Accounts for key rotation

* added aptos keystore to orm validation

* Addded Change logs

* Updated keys implementation to remove address

* Revert "updated go mod"

This reverts commit a8f7e84.

* Update changeset

* Updated GraphQL to include Aptos

* Linting fixes

* added aptos configs for keystore

* Changed Aptos config to RawConfig

* Added Mock file for general config

* using *bool type for aptos enabled

* added debug for error

* Fixed tests + added ORM

* Added Test Scripts

* Updated type of raw config

* update all packages go mod tidy

* update all packages go mod

* Update core/services/chainlink/config.go

Co-authored-by: Jordan Krage <[email protected]>

* Removed Address in key

* Fixed merge conflicts

* nit changes based on reviews

* Added typecast check for keyring

* Added config type cast check

* make generate update version

* update validation method

* update config validation

* Removed Pointer type for RawConfig

* Updated Validate with better error message

* Added Error config

* Update struct of RawConfig

* removed uncessary config validation

* Added missing starknet keystore tests

* added styling feedback

* Update core/services/keystore/keys/aptoskey/account.go

Co-authored-by: Jordan Krage <[email protected]>

* Removed empty array table for config

* fixed lint

---------

Co-authored-by: Jordan Krage <[email protected]>
  • Loading branch information
yongkangchia and jmank88 authored Jul 2, 2024
1 parent 0b7d2c4 commit 2c2ca6a
Show file tree
Hide file tree
Showing 45 changed files with 1,737 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/real-fireants-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

#changed Added Aptos Keystore to Core. This includes Aptos Key which uses ED25519, Keystore, Relevant tests
1 change: 1 addition & 0 deletions core/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ func NewApp(s *Shell) *cli.App {
keysCommand("Cosmos", NewCosmosKeysClient(s)),
keysCommand("Solana", NewSolanaKeysClient(s)),
keysCommand("StarkNet", NewStarkNetKeysClient(s)),
keysCommand("Aptos", NewAptosKeysClient(s)),
keysCommand("DKGSign", NewDKGSignKeysClient(s)),
keysCommand("DKGEncrypt", NewDKGEncryptKeysClient(s)),

Expand Down
57 changes: 57 additions & 0 deletions core/cmd/aptos_keys_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"github.com/smartcontractkit/chainlink-common/pkg/utils"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/aptoskey"
"github.com/smartcontractkit/chainlink/v2/core/web/presenters"
)

type AptosKeyPresenter struct {
JAID
presenters.AptosKeyResource
}

// RenderTable implements TableRenderer
func (p AptosKeyPresenter) RenderTable(rt RendererTable) error {
headers := []string{"ID", "Aptos Public Key"}
rows := [][]string{p.ToRow()}

if _, err := rt.Write([]byte("🔑 Aptos Keys\n")); err != nil {
return err
}
renderList(headers, rows, rt.Writer)

return utils.JustError(rt.Write([]byte("\n")))
}

func (p *AptosKeyPresenter) ToRow() []string {
row := []string{
p.ID,
p.PubKey,
}

return row
}

type AptosKeyPresenters []AptosKeyPresenter

// RenderTable implements TableRenderer
func (ps AptosKeyPresenters) RenderTable(rt RendererTable) error {
headers := []string{"ID", "Aptos Public Key"}
rows := [][]string{}

for _, p := range ps {
rows = append(rows, p.ToRow())
}

if _, err := rt.Write([]byte("🔑 Aptos Keys\n")); err != nil {
return err
}
renderList(headers, rows, rt.Writer)

return utils.JustError(rt.Write([]byte("\n")))
}

func NewAptosKeysClient(s *Shell) KeysClient {
return newKeysClient[aptoskey.Key, AptosKeyPresenter, AptosKeyPresenters]("Aptos", s)
}
175 changes: 175 additions & 0 deletions core/cmd/aptos_keys_commands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package cmd_test

import (
"bytes"
"context"
"flag"
"fmt"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"

"github.com/smartcontractkit/chainlink-common/pkg/utils"
"github.com/smartcontractkit/chainlink/v2/core/cmd"
"github.com/smartcontractkit/chainlink/v2/core/internal/cltest"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/aptoskey"
"github.com/smartcontractkit/chainlink/v2/core/web/presenters"
)

func TestAptosKeyPresenter_RenderTable(t *testing.T) {
t.Parallel()

var (
id = "1"
pubKey = "somepubkey"
buffer = bytes.NewBufferString("")
r = cmd.RendererTable{Writer: buffer}
)

p := cmd.AptosKeyPresenter{
JAID: cmd.JAID{ID: id},
AptosKeyResource: presenters.AptosKeyResource{
JAID: presenters.NewJAID(id),
PubKey: pubKey,
},
}

// Render a single resource
require.NoError(t, p.RenderTable(r))

output := buffer.String()
assert.Contains(t, output, id)
assert.Contains(t, output, pubKey)

// Render many resources
buffer.Reset()
ps := cmd.AptosKeyPresenters{p}
require.NoError(t, ps.RenderTable(r))

output = buffer.String()
assert.Contains(t, output, id)
assert.Contains(t, output, pubKey)
}

func TestShell_AptosKeys(t *testing.T) {
app := startNewApplicationV2(t, nil)
ks := app.GetKeyStore().Aptos()
cleanup := func() {
ctx := context.Background()
keys, err := ks.GetAll()
require.NoError(t, err)
for _, key := range keys {
require.NoError(t, utils.JustError(ks.Delete(ctx, key.ID())))
}
requireAptosKeyCount(t, app, 0)
}

t.Run("ListAptosKeys", func(tt *testing.T) {
defer cleanup()
ctx := testutils.Context(t)
client, r := app.NewShellAndRenderer()
key, err := app.GetKeyStore().Aptos().Create(ctx)
require.NoError(t, err)
requireAptosKeyCount(t, app, 1)
assert.Nil(t, cmd.NewAptosKeysClient(client).ListKeys(cltest.EmptyCLIContext()))
require.Equal(t, 1, len(r.Renders))
keys := *r.Renders[0].(*cmd.AptosKeyPresenters)
assert.True(t, key.PublicKeyStr() == keys[0].PubKey)
})

t.Run("CreateAptosKey", func(tt *testing.T) {
defer cleanup()
client, _ := app.NewShellAndRenderer()
require.NoError(t, cmd.NewAptosKeysClient(client).CreateKey(nilContext))
keys, err := app.GetKeyStore().Aptos().GetAll()
require.NoError(t, err)
require.Len(t, keys, 1)
})

t.Run("DeleteAptosKey", func(tt *testing.T) {
defer cleanup()
ctx := testutils.Context(t)
client, _ := app.NewShellAndRenderer()
key, err := app.GetKeyStore().Aptos().Create(ctx)
require.NoError(t, err)
requireAptosKeyCount(t, app, 1)
set := flag.NewFlagSet("test", 0)
flagSetApplyFromAction(cmd.NewAptosKeysClient(client).DeleteKey, set, "aptos")

require.NoError(tt, set.Set("yes", "true"))

strID := key.ID()
err = set.Parse([]string{strID})
require.NoError(t, err)
c := cli.NewContext(nil, set, nil)
err = cmd.NewAptosKeysClient(client).DeleteKey(c)
require.NoError(t, err)
requireAptosKeyCount(t, app, 0)
})

t.Run("ImportExportAptosKey", func(tt *testing.T) {
defer cleanup()
defer deleteKeyExportFile(t)
ctx := testutils.Context(t)
client, _ := app.NewShellAndRenderer()

_, err := app.GetKeyStore().Aptos().Create(ctx)
require.NoError(t, err)

keys := requireAptosKeyCount(t, app, 1)
key := keys[0]
keyName := keyNameForTest(t)

// Export test invalid id
set := flag.NewFlagSet("test Aptos export", 0)
flagSetApplyFromAction(cmd.NewAptosKeysClient(client).ExportKey, set, "aptos")

require.NoError(tt, set.Parse([]string{"0"}))
require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt"))
require.NoError(tt, set.Set("output", keyName))

c := cli.NewContext(nil, set, nil)
err = cmd.NewAptosKeysClient(client).ExportKey(c)
require.Error(t, err, "Error exporting")
require.Error(t, utils.JustError(os.Stat(keyName)))

// Export test
set = flag.NewFlagSet("test Aptos export", 0)
flagSetApplyFromAction(cmd.NewAptosKeysClient(client).ExportKey, set, "aptos")

require.NoError(tt, set.Parse([]string{fmt.Sprint(key.ID())}))
require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt"))
require.NoError(tt, set.Set("output", keyName))

c = cli.NewContext(nil, set, nil)

require.NoError(t, cmd.NewAptosKeysClient(client).ExportKey(c))
require.NoError(t, utils.JustError(os.Stat(keyName)))

require.NoError(t, utils.JustError(app.GetKeyStore().Aptos().Delete(ctx, key.ID())))
requireAptosKeyCount(t, app, 0)

set = flag.NewFlagSet("test Aptos import", 0)
flagSetApplyFromAction(cmd.NewAptosKeysClient(client).ImportKey, set, "aptos")

require.NoError(tt, set.Parse([]string{keyName}))
require.NoError(tt, set.Set("old-password", "../internal/fixtures/incorrect_password.txt"))
c = cli.NewContext(nil, set, nil)
require.NoError(t, cmd.NewAptosKeysClient(client).ImportKey(c))

requireAptosKeyCount(t, app, 1)
})
}

func requireAptosKeyCount(t *testing.T, app chainlink.Application, length int) []aptoskey.Key {
t.Helper()
keys, err := app.GetKeyStore().Aptos().GetAll()
require.NoError(t, err)
require.Len(t, keys, length)
return keys
}
1 change: 1 addition & 0 deletions core/config/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type AppConfig interface {
CosmosEnabled() bool
SolanaEnabled() bool
StarkNetEnabled() bool
AptosEnabled() bool

Validate() error
ValidateDB() error
Expand Down
2 changes: 2 additions & 0 deletions core/internal/cltest/cltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/aptoskey"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/cosmoskey"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/dkgencryptkey"
Expand Down Expand Up @@ -126,6 +127,7 @@ var (
DefaultP2PKey = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(KeyBigIntSeed))
DefaultSolanaKey = solkey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed))
DefaultStarkNetKey = starkkey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed))
DefaultAptosKey = aptoskey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed))
DefaultVRFKey = vrfkey.MustNewV2XXXTestingOnly(big.NewInt(KeyBigIntSeed))
DefaultDKGSignKey = dkgsignkey.MustNewXXXTestingOnly(big.NewInt(KeyBigIntSeed))
DefaultDKGEncryptKey = dkgencryptkey.MustNewXXXTestingOnly(big.NewInt(KeyBigIntSeed))
Expand Down
26 changes: 26 additions & 0 deletions core/services/chainlink/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ type Config struct {
Solana solcfg.TOMLConfigs `toml:",omitempty"`

Starknet stkcfg.TOMLConfigs `toml:",omitempty"`

Aptos RawConfigs `toml:",omitempty"`
}

// RawConfigs is a list of RawConfig.
type RawConfigs []RawConfig

// RawConfig is the config used for chains that are not embedded.
type RawConfig map[string]any

// ValidateConfig returns an error if the Config is not valid for use, as-is.
func (c *RawConfig) ValidateConfig() (err error) {
if v, ok := (*c)["Enabled"]; ok {
if _, ok := v.(*bool); !ok {
err = multierr.Append(err, commonconfig.ErrInvalid{Name: "Enabled", Value: v, Msg: "expected *bool"})
}
}
return err
}

func (c *RawConfig) IsEnabled() bool {
if c == nil {
return false
}

return (*c)["Enabled"] == nil || *(*c)["Enabled"].(*bool)
}

// TOMLString returns a TOML encoded string.
Expand Down
9 changes: 9 additions & 0 deletions core/services/chainlink/config_general.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,15 @@ func (g *generalConfig) StarkNetEnabled() bool {
return false
}

func (g *generalConfig) AptosEnabled() bool {
for _, c := range g.c.Aptos {
if c.IsEnabled() {
return true
}
}
return false
}

func (g *generalConfig) WebServer() config.WebServer {
return &webServerConfig{c: g.c.WebServer, s: g.secrets.WebServer, rootDir: g.RootDir}
}
Expand Down
5 changes: 3 additions & 2 deletions core/services/chainlink/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ func TestConfig_Validate(t *testing.T) {
toml string
exp string
}{
{name: "invalid", toml: invalidTOML, exp: `invalid configuration: 7 errors:
{name: "invalid", toml: invalidTOML, exp: `invalid configuration: 8 errors:
- P2P.V2.Enabled: invalid value (false): P2P required for OCR or OCR2. Please enable P2P or disable OCR/OCR2.
- Database.Lock.LeaseRefreshInterval: invalid value (6s): must be less than or equal to half of LeaseDuration (10s)
- WebServer: 8 errors:
Expand Down Expand Up @@ -1357,7 +1357,8 @@ func TestConfig_Validate(t *testing.T) {
- 0.ChainID: missing: required for all chains
- 1: 2 errors:
- ChainID: missing: required for all chains
- Nodes: missing: must have at least one node`},
- Nodes: missing: must have at least one node
- Aptos.0.Enabled: invalid value (1): expected *bool`},
} {
t.Run(tt.name, func(t *testing.T) {
var c Config
Expand Down
18 changes: 18 additions & 0 deletions core/services/chainlink/mocks/general_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions core/services/chainlink/testdata/config-invalid.toml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ APIKey = 'key'

[[Starknet]]

[[Aptos]]
Enabled = 1

[OCR2]
Enabled = true

Expand Down
12 changes: 12 additions & 0 deletions core/services/job/job_orm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,18 @@ func TestORM_ValidateKeyStoreMatch(t *testing.T) {
require.NoError(t, err)
})

t.Run(("test Aptos key validation"), func(t *testing.T) {
ctx := testutils.Context(t)
jb.OCR2OracleSpec.Relay = relay.NetworkAptos
err := job.ValidateKeyStoreMatch(ctx, jb.OCR2OracleSpec, keyStore, "bad key")
require.EqualError(t, err, "no Aptos key matching: \"bad key\"")

aptosKey, err := keyStore.Aptos().Create(ctx)
require.NoError(t, err)
err = job.ValidateKeyStoreMatch(ctx, jb.OCR2OracleSpec, keyStore, aptosKey.ID())
require.NoError(t, err)
})

t.Run("test Mercury ETH key validation", func(t *testing.T) {
ctx := testutils.Context(t)
jb.OCR2OracleSpec.PluginType = types.Mercury
Expand Down
Loading

0 comments on commit 2c2ca6a

Please sign in to comment.