-
Notifications
You must be signed in to change notification settings - Fork 10
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 Wallet APIs for local development #335
Changes from all commits
6be440f
587250d
6e2350f
123e4f1
92f6f3b
cb4df85
7c03957
c2e9016
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
evmEmulator "github.com/onflow/flow-go/fvm/evm/emulator" | ||
"github.com/onflow/go-ethereum/accounts" | ||
"github.com/onflow/go-ethereum/common" | ||
"github.com/onflow/go-ethereum/common/hexutil" | ||
"github.com/onflow/go-ethereum/core/types" | ||
"github.com/onflow/go-ethereum/crypto" | ||
"github.com/onflow/go-ethereum/rpc" | ||
|
||
"github.com/onflow/flow-evm-gateway/config" | ||
) | ||
|
||
type WalletAPI struct { | ||
net *BlockChainAPI | ||
config *config.Config | ||
} | ||
|
||
func NewWalletAPI(config *config.Config, net *BlockChainAPI) *WalletAPI { | ||
return &WalletAPI{ | ||
net: net, | ||
config: config, | ||
} | ||
} | ||
|
||
// Accounts returns the collection of accounts this node manages. | ||
func (w *WalletAPI) Accounts() ([]common.Address, error) { | ||
return []common.Address{ | ||
crypto.PubkeyToAddress(w.config.WalletKey.PublicKey), | ||
}, nil | ||
} | ||
|
||
// Sign calculates an ECDSA signature for: | ||
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message). | ||
// | ||
// Note, the produced signature conforms to the secp256k1 curve R, S and V values, | ||
// where the V value will be 27 or 28 for legacy reasons. | ||
// | ||
// The account associated with addr must be unlocked. | ||
// | ||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign | ||
func (w *WalletAPI) Sign( | ||
addr common.Address, | ||
data hexutil.Bytes, | ||
) (hexutil.Bytes, error) { | ||
// Transform the given message to the following format: | ||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) | ||
hash := accounts.TextHash(data) | ||
// Sign the hash using plain ECDSA operations | ||
signature, err := crypto.Sign(hash, w.config.WalletKey) | ||
if err == nil { | ||
// Transform V from 0/1 to 27/28 according to the yellow paper | ||
signature[64] += 27 | ||
} | ||
|
||
return signature, err | ||
} | ||
|
||
// SignTransaction will sign the given transaction with the from account. | ||
// The node needs to have the private key of the account corresponding with | ||
// the given from address and it needs to be unlocked. | ||
func (w *WalletAPI) SignTransaction( | ||
ctx context.Context, | ||
args TransactionArgs, | ||
) (*SignTransactionResult, error) { | ||
if args.Gas == nil { | ||
return nil, errors.New("gas not specified") | ||
} | ||
if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) { | ||
return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas") | ||
} | ||
|
||
accounts, err := w.Accounts() | ||
if err != nil { | ||
return nil, err | ||
} | ||
from := accounts[0] | ||
|
||
nonce := uint64(0) | ||
if args.Nonce != nil { | ||
nonce = uint64(*args.Nonce) | ||
} else { | ||
num := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) | ||
n, err := w.net.GetTransactionCount(ctx, from, &num) | ||
if err != nil { | ||
return nil, err | ||
} | ||
nonce = uint64(*n) | ||
} | ||
|
||
var data []byte | ||
if args.Data != nil { | ||
data = *args.Data | ||
} | ||
|
||
tx := types.NewTx(&types.LegacyTx{ | ||
Nonce: nonce, | ||
To: args.To, | ||
Value: args.Value.ToInt(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, regarding:
|
||
Gas: uint64(*args.Gas), | ||
GasPrice: args.GasPrice.ToInt(), | ||
Data: data, | ||
}) | ||
|
||
signed, err := types.SignTx(tx, evmEmulator.GetDefaultSigner(), w.config.WalletKey) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically, I think we should be signing with the address in |
||
if err != nil { | ||
return nil, fmt.Errorf("error signing EVM transaction: %w", err) | ||
} | ||
|
||
raw, err := signed.MarshalBinary() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &SignTransactionResult{ | ||
Raw: raw, | ||
Tx: tx, | ||
}, nil | ||
} | ||
|
||
// SendTransaction creates a transaction for the given argument, sign it | ||
// and submit it to the transaction pool. | ||
func (w *WalletAPI) SendTransaction( | ||
ctx context.Context, | ||
args TransactionArgs, | ||
) (common.Hash, error) { | ||
signed, err := w.SignTransaction(ctx, args) | ||
if err != nil { | ||
return common.Hash{}, err | ||
} | ||
|
||
return w.net.SendRawTransaction(ctx, signed.Raw) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -344,11 +344,17 @@ func startServer( | |||||||||||||||||||||||
debugAPI = api.NewDebugAPI(trace, blocks, logger) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
var walletAPI *api.WalletAPI | ||||||||||||||||||||||||
if cfg.WalletEnabled { | ||||||||||||||||||||||||
walletAPI = api.NewWalletAPI(cfg, blockchainAPI) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Comment on lines
+347
to
+350
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for The if cfg.WalletEnabled {
walletAPI = api.NewWalletAPI(cfg, blockchainAPI)
+ if walletAPI == nil {
+ return fmt.Errorf("failed to initialize wallet API")
+ }
} Committable suggestion
Suggested change
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
supportedAPIs := api.SupportedAPIs( | ||||||||||||||||||||||||
blockchainAPI, | ||||||||||||||||||||||||
streamAPI, | ||||||||||||||||||||||||
pullAPI, | ||||||||||||||||||||||||
debugAPI, | ||||||||||||||||||||||||
walletAPI, | ||||||||||||||||||||||||
cfg, | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||
package config | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||||||||||||||
"crypto/ecdsa" | ||||||||||||||||||||||||||||||||||||||||||
"flag" | ||||||||||||||||||||||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||||||||||||||||||||||
"io" | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -16,6 +17,7 @@ import ( | |||||||||||||||||||||||||||||||||||||||||
"github.com/onflow/flow-go/fvm/evm/types" | ||||||||||||||||||||||||||||||||||||||||||
flowGo "github.com/onflow/flow-go/model/flow" | ||||||||||||||||||||||||||||||||||||||||||
"github.com/onflow/go-ethereum/common" | ||||||||||||||||||||||||||||||||||||||||||
gethCrypto "github.com/onflow/go-ethereum/crypto" | ||||||||||||||||||||||||||||||||||||||||||
"github.com/rs/zerolog" | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
@@ -85,13 +87,37 @@ type Config struct { | |||||||||||||||||||||||||||||||||||||||||
TracesBucketName string | ||||||||||||||||||||||||||||||||||||||||||
// TracesEnabled sets whether the node is supporting transaction traces. | ||||||||||||||||||||||||||||||||||||||||||
TracesEnabled bool | ||||||||||||||||||||||||||||||||||||||||||
// WalletEnabled sets whether wallet APIs are enabled | ||||||||||||||||||||||||||||||||||||||||||
WalletEnabled bool | ||||||||||||||||||||||||||||||||||||||||||
// WalletKey used for signing transactions | ||||||||||||||||||||||||||||||||||||||||||
WalletKey *ecdsa.PrivateKey | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
func FromFlags() (*Config, error) { | ||||||||||||||||||||||||||||||||||||||||||
cfg := &Config{} | ||||||||||||||||||||||||||||||||||||||||||
var evmNetwork, coinbase, gas, coa, key, keysPath, flowNetwork, logLevel, logWriter, filterExpiry, accessSporkHosts, cloudKMSKeys, cloudKMSProjectID, cloudKMSLocationID, cloudKMSKeyRingID string | ||||||||||||||||||||||||||||||||||||||||||
var streamTimeout int | ||||||||||||||||||||||||||||||||||||||||||
var initHeight, forceStartHeight uint64 | ||||||||||||||||||||||||||||||||||||||||||
var ( | ||||||||||||||||||||||||||||||||||||||||||
evmNetwork, | ||||||||||||||||||||||||||||||||||||||||||
coinbase, | ||||||||||||||||||||||||||||||||||||||||||
gas, | ||||||||||||||||||||||||||||||||||||||||||
coa, | ||||||||||||||||||||||||||||||||||||||||||
key, | ||||||||||||||||||||||||||||||||||||||||||
keysPath, | ||||||||||||||||||||||||||||||||||||||||||
flowNetwork, | ||||||||||||||||||||||||||||||||||||||||||
logLevel, | ||||||||||||||||||||||||||||||||||||||||||
logWriter, | ||||||||||||||||||||||||||||||||||||||||||
filterExpiry, | ||||||||||||||||||||||||||||||||||||||||||
accessSporkHosts, | ||||||||||||||||||||||||||||||||||||||||||
cloudKMSKeys, | ||||||||||||||||||||||||||||||||||||||||||
cloudKMSProjectID, | ||||||||||||||||||||||||||||||||||||||||||
cloudKMSLocationID, | ||||||||||||||||||||||||||||||||||||||||||
cloudKMSKeyRingID, | ||||||||||||||||||||||||||||||||||||||||||
walletKey string | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
streamTimeout int | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
initHeight, | ||||||||||||||||||||||||||||||||||||||||||
forceStartHeight uint64 | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// parse from flags | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&cfg.DatabaseDir, "database-dir", "./db", "Path to the directory for the database") | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -116,13 +142,14 @@ func FromFlags() (*Config, error) { | |||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&cfg.AddressHeader, "address-header", "", "Address header that contains the client IP, this is useful when the server is behind a proxy that sets the source IP of the client. Leave empty if no proxy is used.") | ||||||||||||||||||||||||||||||||||||||||||
flag.Uint64Var(&cfg.HeartbeatInterval, "heartbeat-interval", 100, "Heartbeat interval for AN event subscription") | ||||||||||||||||||||||||||||||||||||||||||
flag.IntVar(&streamTimeout, "stream-timeout", 3, "Defines the timeout in seconds the server waits for the event to be sent to the client") | ||||||||||||||||||||||||||||||||||||||||||
flag.Uint64Var(&forceStartHeight, "force-start-height", 0, "Force set starting Cadence height. This should only be used locally or for testing, never in production.") | ||||||||||||||||||||||||||||||||||||||||||
flag.Uint64Var(&forceStartHeight, "force-start-height", 0, "Force set starting Cadence height. WARNING: This should only be used locally or for testing, never in production.") | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&filterExpiry, "filter-expiry", "5m", "Filter defines the time it takes for an idle filter to expire") | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&cfg.TracesBucketName, "traces-gcp-bucket", "", "GCP bucket name where transaction traces are stored") | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&cloudKMSProjectID, "coa-cloud-kms-project-id", "", "The project ID containing the KMS keys, e.g. 'flow-evm-gateway'") | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&cloudKMSLocationID, "coa-cloud-kms-location-id", "", "The location ID where the key ring is grouped into, e.g. 'global'") | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&cloudKMSKeyRingID, "coa-cloud-kms-key-ring-id", "", "The key ring ID where the KMS keys exist, e.g. 'tx-signing'") | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&cloudKMSKeys, "coa-cloud-kms-keys", "", `Names of the KMS keys and their versions as a comma separated list, e.g. "gw-key-6@1,gw-key-7@1,gw-key-8@1"`) | ||||||||||||||||||||||||||||||||||||||||||
flag.StringVar(&walletKey, "wallet-api-key", "", "ECDSA private key used for wallet APIs. WARNING: This should only be used locally or for testing, never in production.") | ||||||||||||||||||||||||||||||||||||||||||
flag.Parse() | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
if coinbase == "" { | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -262,6 +289,16 @@ func FromFlags() (*Config, error) { | |||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
cfg.TracesEnabled = cfg.TracesBucketName != "" | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
if walletKey != "" { | ||||||||||||||||||||||||||||||||||||||||||
var k, err = gethCrypto.HexToECDSA(walletKey) | ||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("wrong private key for wallet API: %w", err) | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
cfg.WalletKey = k | ||||||||||||||||||||||||||||||||||||||||||
cfg.WalletEnabled = true | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+292
to
+300
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation for The if walletKey != "" {
var k, err = gethCrypto.HexToECDSA(walletKey)
if err != nil {
return nil, fmt.Errorf("wrong private key for wallet API: %w", err)
}
cfg.WalletKey = k
cfg.WalletEnabled = true
+} else {
+ cfg.WalletEnabled = false
} Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// todo validate Config values | ||||||||||||||||||||||||||||||||||||||||||
return cfg, nil | ||||||||||||||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for unset
WalletKey
.The
Accounts
function should handle the case whereWalletKey
is not set to avoid potential runtime errors.Committable suggestion