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

Add P-Chain address derivation support #118

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
24 changes: 17 additions & 7 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,27 @@ func main() {
cfg.NetworkName = networkName
}

network := &types.NetworkIdentifier{
networkP := &types.NetworkIdentifier{
Blockchain: service.BlockchainName,
Network: cfg.NetworkName,
SubNetworkIdentifier: &types.SubNetworkIdentifier{
Network: mapper.PChainNetworkIdentifier,
},
}
networkC := &types.NetworkIdentifier{
Blockchain: service.BlockchainName,
Network: cfg.NetworkName,
}

asserter, err := asserter.NewServer(
mapper.OperationTypes, // supported operation types
true, // historical balance lookup
[]*types.NetworkIdentifier{network}, // supported networks
[]string{}, // call methods
false, // mempool coins
mapper.OperationTypes, // supported operation types
true, // historical balance lookup
[]*types.NetworkIdentifier{ // supported networks
networkP,
networkC,
},
[]string{}, // call methods
false, // mempool coins
)
if err != nil {
log.Fatal("server asserter init error:", err)
Expand All @@ -130,7 +140,7 @@ func main() {
serviceConfig := &service.Config{
Mode: cfg.Mode,
ChainID: big.NewInt(cfg.ChainID),
NetworkID: network,
NetworkID: networkC,
GenesisBlockHash: cfg.GenesisBlockHash,
AvaxAssetID: assetID,
AP5Activation: AP5Activation,
Expand Down
38 changes: 37 additions & 1 deletion mapper/helper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
package mapper

import "strings"
import (
"errors"
"strings"

"github.com/ava-labs/avalanchego/utils/constants"
"github.com/coinbase/rosetta-sdk-go/types"
)

var errUnsupportedChain = errors.New("unsupported chain")
var errUnsupportedNetwork = errors.New("unsupported network")
xiaoxiaff marked this conversation as resolved.
Show resolved Hide resolved

// EqualFoldContains checks if the array contains the string regardless of casing
func EqualFoldContains(arr []string, str string) bool {
Expand All @@ -11,3 +20,30 @@ func EqualFoldContains(arr []string, str string) bool {
}
return false
}

// IsPChain checks network identifier to make sure sub-network identifier set to "P"
func IsPChain(networkIdentifier *types.NetworkIdentifier) bool {
return networkIdentifier != nil &&
networkIdentifier.SubNetworkIdentifier != nil &&
networkIdentifier.SubNetworkIdentifier.Network == PChainNetworkIdentifier
}

// GetAliasAndHRP fetches chain id alias and hrp for address formatting.
// Right now only P chain id alias is supported
func GetAliasAndHRP(networkIdentifier *types.NetworkIdentifier) (string, string, error) {
if !IsPChain(networkIdentifier) {
return "", "", errUnsupportedChain
}

var hrp string
switch networkIdentifier.Network {
case FujiNetwork:
hrp = constants.GetHRP(constants.FujiID)
case MainnetNetwork:
hrp = constants.GetHRP(constants.MainnetID)
default:
return "", "", errUnsupportedNetwork
}

return PChainIDAlias, hrp, nil
xiaoxiaff marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 6 additions & 2 deletions mapper/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ package mapper

import (
"github.com/ava-labs/coreth/params"
"github.com/ethereum/go-ethereum/common"

"github.com/coinbase/rosetta-sdk-go/types"
"github.com/ethereum/go-ethereum/common"
)

const (
MainnetChainID = 43114
MainnetAssetID = "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z"
MainnetNetwork = "Mainnet"

FujiChainID = 43113
FujiAssetID = "U8iRqJoiJm8xZHAacmvYyZVwqQx6uDNtQeP3CQ6fcgQk3JqnK"
FujiNetwork = "Fuji"

ContractAddressMetadata = "contractAddress"
IndexTransferredMetadata = "indexTransferred"

PChainNetworkIdentifier = "P"
PChainIDAlias = "P"

OpCall = "CALL"
OpFee = "FEE"
OpCreate = "CREATE"
Expand Down
46 changes: 46 additions & 0 deletions service/chain/p/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package p

import (
"context"

"github.com/ava-labs/avalanche-rosetta/mapper"
"github.com/ava-labs/avalanchego/utils/crypto"
"github.com/ava-labs/avalanchego/utils/formatting/address"
"github.com/coinbase/rosetta-sdk-go/types"
)

type Client struct {
fac crypto.FactorySECP256K1R
}

func NewClient() *Client {
return &Client{
fac: crypto.FactorySECP256K1R{},
}
}

func (c *Client) DeriveAddress(
ctx context.Context,
req *types.ConstructionDeriveRequest,
) (*types.ConstructionDeriveResponse, error) {
pub, err := c.fac.ToPublicKey(req.PublicKey.Bytes)
if err != nil {
return nil, err
}

chainIDAlias, hrp, getErr := mapper.GetAliasAndHRP(req.NetworkIdentifier)
if getErr != nil {
return nil, getErr
}

addr, err := address.Format(chainIDAlias, hrp, pub.Address().Bytes())
if err != nil {
return nil, err
}

return &types.ConstructionDeriveResponse{
AccountIdentifier: &types.AccountIdentifier{
Address: addr,
},
}, nil
}
6 changes: 3 additions & 3 deletions service/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
"math/big"
"strings"

"github.com/ava-labs/avalanche-rosetta/client"
"github.com/coinbase/rosetta-sdk-go/types"

ethtypes "github.com/ava-labs/coreth/core/types"
"github.com/coinbase/rosetta-sdk-go/types"
ethcommon "github.com/ethereum/go-ethereum/common"

"github.com/ava-labs/avalanche-rosetta/client"
)

const (
Expand Down
23 changes: 17 additions & 6 deletions service/service_construction.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import (
"fmt"
"math/big"

ethtypes "github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/interfaces"
"github.com/coinbase/rosetta-sdk-go/parser"
"github.com/coinbase/rosetta-sdk-go/server"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/coinbase/rosetta-sdk-go/utils"
"golang.org/x/crypto/sha3"

ethtypes "github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/interfaces"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"golang.org/x/crypto/sha3"

"github.com/ava-labs/avalanche-rosetta/client"
"github.com/ava-labs/avalanche-rosetta/mapper"
"github.com/ethereum/go-ethereum/common/hexutil"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ava-labs/avalanche-rosetta/service/chain/p"
)

const (
Expand All @@ -33,13 +33,15 @@ const (
type ConstructionService struct {
config *Config
client client.Client
p *p.Client
}

// NewConstructionService returns a new construction servicer
func NewConstructionService(config *Config, client client.Client) server.ConstructionAPIServicer {
return &ConstructionService{
config: config,
client: client,
p: p.NewClient(),
}
}

Expand Down Expand Up @@ -226,6 +228,15 @@ func (s ConstructionService) ConstructionDerive(
return nil, wrapError(errInvalidInput, "public key is not provided")
}

if mapper.IsPChain(req.NetworkIdentifier) {
xiaoxiaff marked this conversation as resolved.
Show resolved Hide resolved
res, err := s.p.DeriveAddress(ctx, req)
if err != nil {
return nil, wrapError(errInternalError, "p chain address derivation failed")
}

return res, nil
}

key, err := ethcrypto.DecompressPubkey(req.PublicKey.Bytes)
if err != nil {
return nil, wrapError(errInvalidInput, err)
Expand Down
32 changes: 31 additions & 1 deletion service/service_construction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/ava-labs/avalanche-rosetta/mapper"
mocks "github.com/ava-labs/avalanche-rosetta/mocks/client"
"github.com/ava-labs/avalanche-rosetta/service/chain/p"
"github.com/ava-labs/coreth/interfaces"

"github.com/coinbase/rosetta-sdk-go/types"
Expand Down Expand Up @@ -238,7 +239,9 @@ func TestContructionHash(t *testing.T) {
}

func TestConstructionDerive(t *testing.T) {
service := ConstructionService{}
service := ConstructionService{
p: p.NewClient(),
}

t.Run("no public key", func(t *testing.T) {
resp, err := service.ConstructionDerive(
Expand Down Expand Up @@ -285,6 +288,33 @@ func TestConstructionDerive(t *testing.T) {
resp.AccountIdentifier.Address,
)
})

t.Run("p-chain address", func(t *testing.T) {
src := "02e0d4392cfa224d4be19db416b3cf62e90fb2b7015e7b62a95c8cb490514943f6"
b, _ := hex.DecodeString(src)

resp, err := service.ConstructionDerive(
context.Background(),
&types.ConstructionDeriveRequest{
NetworkIdentifier: &types.NetworkIdentifier{
Network: mapper.FujiNetwork,
SubNetworkIdentifier: &types.SubNetworkIdentifier{
Network: mapper.PChainNetworkIdentifier,
},
},
PublicKey: &types.PublicKey{
Bytes: b,
CurveType: types.Secp256k1,
},
},
)
assert.Nil(t, err)
assert.Equal(
t,
"P-fuji15f9g0h5xkr5cp47n6u3qxj6yjtzzzrdr23a3tl",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how I verified the derivation:

	keyData, err := keychain.ParsePrivateKeyAndDeriveAddresses(wc.runtimeConfig.NetworkConfig, privateKeyString)
	if err != nil {
		return err
	}

	println("Private Key           :", keyData.PrivateKey.String())
	println("Public Key            :", hex.EncodeToString(keyData.PublicKey.Bytes()))
	println("P Chain Bech32 Address   :", keyData.Addresses.P)

Result:

Private Key           : PrivateKey-2aazcWnVv1MicvTkqBhjuCkuhE6jdAN4tkXXMHyucKCzEQrNKm
Public Key            : 02e0d4392cfa224d4be19db416b3cf62e90fb2b7015e7b62a95c8cb490514943f6
P Chain Bech32 Address   : P-fuji15f9g0h5xkr5cp47n6u3qxj6yjtzzzrdr23a3tl

resp.AccountIdentifier.Address,
)
})
}

func forceMarshalMap(t *testing.T, i interface{}) map[string]interface{} {
Expand Down