mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-16 09:50:45 +00:00
parent
69e9f41b4c
commit
b413f71763
8 changed files with 9 additions and 580 deletions
|
|
@ -587,11 +587,6 @@ var (
|
||||||
Usage: "Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC",
|
Usage: "Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC",
|
||||||
Category: flags.APICategory,
|
Category: flags.APICategory,
|
||||||
}
|
}
|
||||||
EnablePersonal = &cli.BoolFlag{
|
|
||||||
Name: "rpc.enabledeprecatedpersonal",
|
|
||||||
Usage: "Enables the (deprecated) personal namespace",
|
|
||||||
Category: flags.APICategory,
|
|
||||||
}
|
|
||||||
BatchRequestLimit = &cli.IntFlag{
|
BatchRequestLimit = &cli.IntFlag{
|
||||||
Name: "rpc-batch-request-limit",
|
Name: "rpc-batch-request-limit",
|
||||||
Usage: "Maximum number of requests in a batch",
|
Usage: "Maximum number of requests in a batch",
|
||||||
|
|
@ -1284,7 +1279,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet(EnablePersonal.Name) {
|
if ctx.IsSet(EnablePersonal.Name) {
|
||||||
cfg.EnablePersonal = true
|
log.Warn(fmt.Sprintf("Option --%s is deprecated. The 'personal' RPC namespace has been removed.", EnablePersonal.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,12 @@ var (
|
||||||
Value: ethconfig.Defaults.LightPeers,
|
Value: ethconfig.Defaults.LightPeers,
|
||||||
Category: flags.DeprecatedCategory,
|
Category: flags.DeprecatedCategory,
|
||||||
}
|
}
|
||||||
|
// Deprecated Oct 2024
|
||||||
|
EnablePersonal = &cli.BoolFlag{
|
||||||
|
Name: "rpc.enabledeprecatedpersonal",
|
||||||
|
Usage: "This used to enable the 'personal' namespace.",
|
||||||
|
Category: flags.DeprecatedCategory,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
|
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,11 @@ package console
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/usbwallet"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
||||||
"github.com/XinFinOrg/XDPoSChain/internal/jsre"
|
"github.com/XinFinOrg/XDPoSChain/internal/jsre"
|
||||||
"github.com/XinFinOrg/XDPoSChain/rpc"
|
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||||
|
|
@ -49,212 +47,6 @@ func newBridge(client *rpc.Client, prompter UserPrompter, printer io.Writer) *br
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getJeth(vm *goja.Runtime) *goja.Object {
|
|
||||||
jeth := vm.Get("jeth")
|
|
||||||
if jeth == nil {
|
|
||||||
panic(vm.ToValue("jeth object does not exist"))
|
|
||||||
}
|
|
||||||
return jeth.ToObject(vm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAccount is a wrapper around the personal.newAccount RPC method that uses a
|
|
||||||
// non-echoing password prompt to acquire the passphrase and executes the original
|
|
||||||
// RPC method (saved in jeth.newAccount) with it to actually execute the RPC call.
|
|
||||||
func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) {
|
|
||||||
var (
|
|
||||||
password string
|
|
||||||
confirm string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch {
|
|
||||||
// No password was specified, prompt the user for it
|
|
||||||
case len(call.Arguments) == 0:
|
|
||||||
if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if password != confirm {
|
|
||||||
return nil, errors.New("passwords don't match")
|
|
||||||
}
|
|
||||||
// A single string password was specified, use that
|
|
||||||
case len(call.Arguments) == 1 && call.Argument(0).ToString() != nil:
|
|
||||||
password = call.Argument(0).ToString().String()
|
|
||||||
default:
|
|
||||||
return nil, errors.New("expected 0 or 1 string argument")
|
|
||||||
}
|
|
||||||
// Password acquired, execute the call and return
|
|
||||||
newAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("newAccount"))
|
|
||||||
if !callable {
|
|
||||||
return nil, errors.New("jeth.newAccount is not callable")
|
|
||||||
}
|
|
||||||
ret, err := newAccount(goja.Null(), call.VM.ToValue(password))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenWallet is a wrapper around personal.openWallet which can interpret and
|
|
||||||
// react to certain error messages, such as the Trezor PIN matrix request.
|
|
||||||
func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) {
|
|
||||||
// Make sure we have a wallet specified to open
|
|
||||||
if call.Argument(0).ToObject(call.VM).ClassName() != "String" {
|
|
||||||
return nil, errors.New("first argument must be the wallet URL to open")
|
|
||||||
}
|
|
||||||
wallet := call.Argument(0)
|
|
||||||
|
|
||||||
var passwd goja.Value
|
|
||||||
if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) {
|
|
||||||
passwd = call.VM.ToValue("")
|
|
||||||
} else {
|
|
||||||
passwd = call.Argument(1)
|
|
||||||
}
|
|
||||||
// Open the wallet and return if successful in itself
|
|
||||||
openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet"))
|
|
||||||
if !callable {
|
|
||||||
return nil, errors.New("jeth.openWallet is not callable")
|
|
||||||
}
|
|
||||||
val, err := openWallet(goja.Null(), wallet, passwd)
|
|
||||||
if err == nil {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wallet open failed, report error unless it's a PIN or PUK entry
|
|
||||||
switch {
|
|
||||||
case strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()):
|
|
||||||
val, err = b.readPinAndReopenWallet(call)
|
|
||||||
if err == nil {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
val, err = b.readPassphraseAndReopenWallet(call)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Unknown error occurred, drop to the user
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bridge) readPassphraseAndReopenWallet(call jsre.Call) (goja.Value, error) {
|
|
||||||
wallet := call.Argument(0)
|
|
||||||
input, err := b.prompter.PromptPassword("Please enter your passphrase: ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet"))
|
|
||||||
if !callable {
|
|
||||||
return nil, errors.New("jeth.openWallet is not callable")
|
|
||||||
}
|
|
||||||
return openWallet(goja.Null(), wallet, call.VM.ToValue(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) {
|
|
||||||
wallet := call.Argument(0)
|
|
||||||
// Trezor PIN matrix input requested, display the matrix to the user and fetch the data
|
|
||||||
fmt.Fprintf(b.printer, "Look at the device for number positions\n\n")
|
|
||||||
fmt.Fprintf(b.printer, "7 | 8 | 9\n")
|
|
||||||
fmt.Fprintf(b.printer, "--+---+--\n")
|
|
||||||
fmt.Fprintf(b.printer, "4 | 5 | 6\n")
|
|
||||||
fmt.Fprintf(b.printer, "--+---+--\n")
|
|
||||||
fmt.Fprintf(b.printer, "1 | 2 | 3\n\n")
|
|
||||||
|
|
||||||
input, err := b.prompter.PromptPassword("Please enter current PIN: ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet"))
|
|
||||||
if !callable {
|
|
||||||
return nil, errors.New("jeth.openWallet is not callable")
|
|
||||||
}
|
|
||||||
return openWallet(goja.Null(), wallet, call.VM.ToValue(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that
|
|
||||||
// uses a non-echoing password prompt to acquire the passphrase and executes the
|
|
||||||
// original RPC method (saved in jeth.unlockAccount) with it to actually execute
|
|
||||||
// the RPC call.
|
|
||||||
func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) {
|
|
||||||
// Make sure we have an account specified to unlock.
|
|
||||||
if call.Argument(0).ExportType().Kind() != reflect.String {
|
|
||||||
return nil, errors.New("first argument must be the account to unlock")
|
|
||||||
}
|
|
||||||
account := call.Argument(0)
|
|
||||||
|
|
||||||
// If password is not given or is the null value, prompt the user for it.
|
|
||||||
var passwd goja.Value
|
|
||||||
if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) {
|
|
||||||
fmt.Fprintf(b.printer, "Unlock account %s\n", account)
|
|
||||||
input, err := b.prompter.PromptPassword("Passphrase: ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
passwd = call.VM.ToValue(input)
|
|
||||||
} else {
|
|
||||||
if call.Argument(1).ExportType().Kind() != reflect.String {
|
|
||||||
return nil, errors.New("password must be a string")
|
|
||||||
}
|
|
||||||
passwd = call.Argument(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Third argument is the duration how long the account should be unlocked.
|
|
||||||
duration := goja.Null()
|
|
||||||
if !goja.IsUndefined(call.Argument(2)) && !goja.IsNull(call.Argument(2)) {
|
|
||||||
if !isNumber(call.Argument(2)) {
|
|
||||||
return nil, errors.New("unlock duration must be a number")
|
|
||||||
}
|
|
||||||
duration = call.Argument(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the request to the backend and return.
|
|
||||||
unlockAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount"))
|
|
||||||
if !callable {
|
|
||||||
return nil, errors.New("jeth.unlockAccount is not callable")
|
|
||||||
}
|
|
||||||
return unlockAccount(goja.Null(), account, passwd, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign is a wrapper around the personal.sign RPC method that uses a non-echoing password
|
|
||||||
// prompt to acquire the passphrase and executes the original RPC method (saved in
|
|
||||||
// jeth.sign) with it to actually execute the RPC call.
|
|
||||||
func (b *bridge) Sign(call jsre.Call) (goja.Value, error) {
|
|
||||||
var (
|
|
||||||
message = call.Argument(0)
|
|
||||||
account = call.Argument(1)
|
|
||||||
passwd = call.Argument(2)
|
|
||||||
)
|
|
||||||
|
|
||||||
if message.ExportType().Kind() != reflect.String {
|
|
||||||
return nil, errors.New("first argument must be the message to sign")
|
|
||||||
}
|
|
||||||
if account.ExportType().Kind() != reflect.String {
|
|
||||||
return nil, errors.New("second argument must be the account to sign with")
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the password is not given or null ask the user and ensure password is a string
|
|
||||||
if goja.IsUndefined(passwd) || goja.IsNull(passwd) {
|
|
||||||
fmt.Fprintf(b.printer, "Give password for account %s\n", account)
|
|
||||||
input, err := b.prompter.PromptPassword("Password: ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
passwd = call.VM.ToValue(input)
|
|
||||||
} else if passwd.ExportType().Kind() != reflect.String {
|
|
||||||
return nil, errors.New("third argument must be the password to unlock the account")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the request to the backend and return
|
|
||||||
sign, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount"))
|
|
||||||
if !callable {
|
|
||||||
return nil, errors.New("jeth.unlockAccount is not callable")
|
|
||||||
}
|
|
||||||
return sign(goja.Null(), message, account, passwd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep will block the console for the specified number of seconds.
|
// Sleep will block the console for the specified number of seconds.
|
||||||
func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) {
|
func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) {
|
||||||
if !isNumber(call.Argument(0)) {
|
if !isNumber(call.Argument(0)) {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/internal/jsre"
|
"github.com/XinFinOrg/XDPoSChain/internal/jsre"
|
||||||
"github.com/XinFinOrg/XDPoSChain/internal/jsre/deps"
|
"github.com/XinFinOrg/XDPoSChain/internal/jsre/deps"
|
||||||
"github.com/XinFinOrg/XDPoSChain/internal/web3ext"
|
"github.com/XinFinOrg/XDPoSChain/internal/web3ext"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/rpc"
|
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
|
|
@ -123,7 +122,6 @@ func (c *Console) init(preload []string) error {
|
||||||
// Add bridge overrides for web3.js functionality.
|
// Add bridge overrides for web3.js functionality.
|
||||||
c.jsre.Do(func(vm *goja.Runtime) {
|
c.jsre.Do(func(vm *goja.Runtime) {
|
||||||
c.initAdmin(vm, bridge)
|
c.initAdmin(vm, bridge)
|
||||||
c.initPersonal(vm, bridge)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Preload JavaScript files.
|
// Preload JavaScript files.
|
||||||
|
|
@ -221,30 +219,6 @@ func (c *Console) initAdmin(vm *goja.Runtime, bridge *bridge) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initPersonal redirects account-related API methods through the bridge.
|
|
||||||
//
|
|
||||||
// If the console is in interactive mode and the 'personal' API is available, override
|
|
||||||
// the openWallet, unlockAccount, newAccount and sign methods since these require user
|
|
||||||
// interaction. The original web3 callbacks are stored in 'jeth'. These will be called
|
|
||||||
// by the bridge after the prompt and send the original web3 request to the backend.
|
|
||||||
func (c *Console) initPersonal(vm *goja.Runtime, bridge *bridge) {
|
|
||||||
personal := getObject(vm, "personal")
|
|
||||||
if personal == nil || c.prompter == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Warn("Enabling deprecated personal namespace")
|
|
||||||
jeth := vm.NewObject()
|
|
||||||
vm.Set("jeth", jeth)
|
|
||||||
jeth.Set("openWallet", personal.Get("openWallet"))
|
|
||||||
jeth.Set("unlockAccount", personal.Get("unlockAccount"))
|
|
||||||
jeth.Set("newAccount", personal.Get("newAccount"))
|
|
||||||
jeth.Set("sign", personal.Get("sign"))
|
|
||||||
personal.Set("openWallet", jsre.MakeCallback(vm, bridge.OpenWallet))
|
|
||||||
personal.Set("unlockAccount", jsre.MakeCallback(vm, bridge.UnlockAccount))
|
|
||||||
personal.Set("newAccount", jsre.MakeCallback(vm, bridge.NewAccount))
|
|
||||||
personal.Set("sign", jsre.MakeCallback(vm, bridge.Sign))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Console) clearHistory() {
|
func (c *Console) clearHistory() {
|
||||||
c.history = nil
|
c.history = nil
|
||||||
c.prompter.ClearHistory()
|
c.prompter.ClearHistory()
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
||||||
math "github.com/XinFinOrg/XDPoSChain/common/math"
|
math "github.com/XinFinOrg/XDPoSChain/common/math"
|
||||||
|
|
@ -293,281 +292,6 @@ func (s *EthereumAccountAPI) Accounts() []common.Address {
|
||||||
return s.am.Accounts()
|
return s.am.Accounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersonalAccountAPI provides an API to access accounts managed by this node.
|
|
||||||
// It offers methods to create, (un)lock en list accounts. Some methods accept
|
|
||||||
// passwords and are therefore considered private by default.
|
|
||||||
type PersonalAccountAPI struct {
|
|
||||||
am *accounts.Manager
|
|
||||||
nonceLock *AddrLocker
|
|
||||||
b Backend
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPersonalAccountAPI create a new PersonalAccountAPI.
|
|
||||||
func NewPersonalAccountAPI(b Backend, nonceLock *AddrLocker) *PersonalAccountAPI {
|
|
||||||
return &PersonalAccountAPI{
|
|
||||||
am: b.AccountManager(),
|
|
||||||
nonceLock: nonceLock,
|
|
||||||
b: b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
|
||||||
func (s *PersonalAccountAPI) ListAccounts() []common.Address {
|
|
||||||
return s.am.Accounts()
|
|
||||||
}
|
|
||||||
|
|
||||||
// rawWallet is a JSON representation of an accounts.Wallet interface, with its
|
|
||||||
// data contents extracted into plain fields.
|
|
||||||
type rawWallet struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Failure string `json:"failure,omitempty"`
|
|
||||||
Accounts []accounts.Account `json:"accounts,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListWallets will return a list of wallets this node manages.
|
|
||||||
func (s *PersonalAccountAPI) ListWallets() []rawWallet {
|
|
||||||
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
|
|
||||||
for _, wallet := range s.am.Wallets() {
|
|
||||||
status, failure := wallet.Status()
|
|
||||||
|
|
||||||
raw := rawWallet{
|
|
||||||
URL: wallet.URL().String(),
|
|
||||||
Status: status,
|
|
||||||
Accounts: wallet.Accounts(),
|
|
||||||
}
|
|
||||||
if failure != nil {
|
|
||||||
raw.Failure = failure.Error()
|
|
||||||
}
|
|
||||||
wallets = append(wallets, raw)
|
|
||||||
}
|
|
||||||
return wallets
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenWallet initiates a hardware wallet opening procedure, establishing a USB
|
|
||||||
// connection and attempting to authenticate via the provided passphrase. Note,
|
|
||||||
// the method may return an extra challenge requiring a second open (e.g. the
|
|
||||||
// Trezor PIN matrix challenge).
|
|
||||||
func (s *PersonalAccountAPI) OpenWallet(url string, passphrase *string) error {
|
|
||||||
wallet, err := s.am.Wallet(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pass := ""
|
|
||||||
if passphrase != nil {
|
|
||||||
pass = *passphrase
|
|
||||||
}
|
|
||||||
return wallet.Open(pass)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeriveAccount requests a HD wallet to derive a new account, optionally pinning
|
|
||||||
// it for later reuse.
|
|
||||||
func (s *PersonalAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
|
|
||||||
wallet, err := s.am.Wallet(url)
|
|
||||||
if err != nil {
|
|
||||||
return accounts.Account{}, err
|
|
||||||
}
|
|
||||||
derivPath, err := accounts.ParseDerivationPath(path)
|
|
||||||
if err != nil {
|
|
||||||
return accounts.Account{}, err
|
|
||||||
}
|
|
||||||
if pin == nil {
|
|
||||||
pin = new(bool)
|
|
||||||
}
|
|
||||||
return wallet.Derive(derivPath, *pin)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAccount will create a new account and returns the address for the new account.
|
|
||||||
func (s *PersonalAccountAPI) NewAccount(password string) (common.AddressEIP55, error) {
|
|
||||||
acc, err := fetchKeystore(s.am).NewAccount(password)
|
|
||||||
if err == nil {
|
|
||||||
addrEIP55 := common.AddressEIP55(acc.Address)
|
|
||||||
log.Info("Your new key was generated", "address", addrEIP55.String())
|
|
||||||
log.Warn("Please backup your key file!", "path", acc.URL.Path)
|
|
||||||
log.Warn("Please remember your password!")
|
|
||||||
return addrEIP55, nil
|
|
||||||
}
|
|
||||||
return common.AddressEIP55{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchKeystore retrives the encrypted keystore from the account manager.
|
|
||||||
func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
|
|
||||||
ks := am.Backends(keystore.KeyStoreType)
|
|
||||||
if len(ks) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ks[0].(*keystore.KeyStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
|
|
||||||
// encrypting it with the passphrase.
|
|
||||||
func (s *PersonalAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
|
|
||||||
key, err := crypto.HexToECDSA(privkey)
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
acc, err := fetchKeystore(s.am).ImportECDSA(key, password)
|
|
||||||
return acc.Address, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnlockAccount will unlock the account associated with the given address with
|
|
||||||
// the given password for duration seconds. If duration is nil it will use a
|
|
||||||
// default of 300 seconds. It returns an indication if the account was unlocked.
|
|
||||||
func (s *PersonalAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) {
|
|
||||||
const max = uint64(time.Duration(gomath.MaxInt64) / time.Second)
|
|
||||||
var d time.Duration
|
|
||||||
if duration == nil {
|
|
||||||
d = 300 * time.Second
|
|
||||||
} else if *duration > max {
|
|
||||||
return false, errors.New("unlock duration too large")
|
|
||||||
} else {
|
|
||||||
d = time.Duration(*duration) * time.Second
|
|
||||||
}
|
|
||||||
err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)
|
|
||||||
return err == nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockAccount will lock the account associated with the given address when it's unlocked.
|
|
||||||
func (s *PersonalAccountAPI) LockAccount(addr common.Address) bool {
|
|
||||||
return fetchKeystore(s.am).Lock(addr) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// signTransactions sets defaults and signs the given transaction
|
|
||||||
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
|
|
||||||
// and release it after the transaction has been submitted to the tx pool
|
|
||||||
func (s *PersonalAccountAPI) signTransaction(ctx context.Context, args *TransactionArgs, passwd string) (*types.Transaction, error) {
|
|
||||||
// Look up the wallet containing the requested signer
|
|
||||||
account := accounts.Account{Address: args.from()}
|
|
||||||
wallet, err := s.am.Find(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Set some sanity defaults and terminate on failure
|
|
||||||
if err := args.setDefaults(ctx, s.b, false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Assemble the transaction and sign with the wallet
|
|
||||||
tx := args.toTransaction()
|
|
||||||
|
|
||||||
var chainID *big.Int
|
|
||||||
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
|
|
||||||
chainID = config.ChainId
|
|
||||||
}
|
|
||||||
return wallet.SignTxWithPassphrase(account, passwd, tx, chainID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTransaction will create a transaction from the given arguments and
|
|
||||||
// tries to sign it with the key associated with args.From. If the given
|
|
||||||
// passwd isn't able to decrypt the key it fails.
|
|
||||||
func (s *PersonalAccountAPI) SendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) {
|
|
||||||
if args.Nonce == nil {
|
|
||||||
// Hold the addresse's mutex around signing to prevent concurrent assignment of
|
|
||||||
// the same nonce to multiple accounts.
|
|
||||||
s.nonceLock.LockAddr(args.from())
|
|
||||||
defer s.nonceLock.UnlockAddr(args.from())
|
|
||||||
}
|
|
||||||
signed, err := s.signTransaction(ctx, &args, passwd)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Failed transaction send attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
|
|
||||||
return common.Hash{}, err
|
|
||||||
}
|
|
||||||
return SubmitTransaction(ctx, s.b, signed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignTransaction will create a transaction from the given arguments and
|
|
||||||
// tries to sign it with the key associated with args.To. If the given passwd isn't
|
|
||||||
// able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast
|
|
||||||
// to other nodes
|
|
||||||
func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args TransactionArgs, passwd string) (*SignTransactionResult, error) {
|
|
||||||
// No need to obtain the noncelock mutex, since we won't be sending this
|
|
||||||
// tx into the transaction pool, but right back to the user
|
|
||||||
if args.From == nil {
|
|
||||||
return nil, errors.New("sender not specified")
|
|
||||||
}
|
|
||||||
if args.Gas == nil {
|
|
||||||
return nil, errors.New("gas not specified")
|
|
||||||
}
|
|
||||||
if args.GasPrice == nil && (args.MaxFeePerGas == nil || args.MaxPriorityFeePerGas == nil) {
|
|
||||||
return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
|
|
||||||
}
|
|
||||||
if args.Nonce == nil {
|
|
||||||
return nil, errors.New("nonce not specified")
|
|
||||||
}
|
|
||||||
// Before actually sign the transaction, ensure the transaction fee is reasonable.
|
|
||||||
tx := args.toTransaction()
|
|
||||||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
signed, err := s.signTransaction(ctx, &args, passwd)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Failed transaction sign attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err := signed.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &SignTransactionResult{data, signed}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign calculates an Ethereum ECDSA signature for:
|
|
||||||
// keccack256("\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 key used to calculate the signature is decrypted with the given password.
|
|
||||||
//
|
|
||||||
// https://github.com/XinFinOrg/XDPoSChain/wiki/Management-APIs#personal_sign
|
|
||||||
func (s *PersonalAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
|
|
||||||
// Look up the wallet containing the requested signer
|
|
||||||
account := accounts.Account{Address: addr}
|
|
||||||
|
|
||||||
wallet, err := s.b.AccountManager().Find(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Assemble sign the data with the wallet
|
|
||||||
signature, err := wallet.SignTextWithPassphrase(account, passwd, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
|
||||||
return signature, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EcRecover returns the address for the account that was used to create the signature.
|
|
||||||
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
|
|
||||||
// the address of:
|
|
||||||
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
|
|
||||||
// addr = ecrecover(hash, signature)
|
|
||||||
//
|
|
||||||
// Note, the signature must conform to the secp256k1 curve R, S and V values, where
|
|
||||||
// the V value must be be 27 or 28 for legacy reasons.
|
|
||||||
//
|
|
||||||
// https://github.com/XinFinOrg/XDPoSChain/wiki/Management-APIs#personal_ecRecover
|
|
||||||
func (s *PersonalAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
|
|
||||||
if len(sig) != crypto.SignatureLength {
|
|
||||||
return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
|
|
||||||
}
|
|
||||||
if sig[crypto.RecoveryIDOffset] != 27 && sig[crypto.RecoveryIDOffset] != 28 {
|
|
||||||
return common.Address{}, errors.New("invalid Ethereum signature (V is not 27 or 28)")
|
|
||||||
}
|
|
||||||
sig[crypto.RecoveryIDOffset] -= 27 // Transform yellow paper V from 27/28 to 0/1
|
|
||||||
|
|
||||||
rpk, err := crypto.SigToPub(accounts.TextHash(data), sig)
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
return crypto.PubkeyToAddress(*rpk), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignAndSendTransaction was renamed to SendTransaction. This method is deprecated
|
|
||||||
// and will be removed in the future. It primary goal is to give clients time to update.
|
|
||||||
func (s *PersonalAccountAPI) SignAndSendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) {
|
|
||||||
return s.SendTransaction(ctx, args, passwd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockChainAPI provides an API to access Ethereum blockchain data.
|
// BlockChainAPI provides an API to access Ethereum blockchain data.
|
||||||
type BlockChainAPI struct {
|
type BlockChainAPI struct {
|
||||||
b Backend
|
b Backend
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,6 @@ func GetAPIs(apiBackend Backend, chainReader consensus.ChainReader) []rpc.API {
|
||||||
}, {
|
}, {
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Service: NewEthereumAccountAPI(apiBackend.AccountManager()),
|
Service: NewEthereumAccountAPI(apiBackend.AccountManager()),
|
||||||
}, {
|
|
||||||
Namespace: "personal",
|
|
||||||
Service: NewPersonalAccountAPI(apiBackend, nonceLock),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ var Modules = map[string]string{
|
||||||
"eth": Eth_JS,
|
"eth": Eth_JS,
|
||||||
"miner": Miner_JS,
|
"miner": Miner_JS,
|
||||||
"net": Net_JS,
|
"net": Net_JS,
|
||||||
"personal": Personal_JS,
|
|
||||||
"rpc": RPC_JS,
|
"rpc": RPC_JS,
|
||||||
"shh": Shh_JS,
|
"shh": Shh_JS,
|
||||||
"XDCx": XDCX_JS,
|
"XDCx": XDCX_JS,
|
||||||
|
|
@ -648,52 +647,6 @@ web3._extend({
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
|
|
||||||
const Personal_JS = `
|
|
||||||
web3._extend({
|
|
||||||
property: 'personal',
|
|
||||||
methods: [
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'importRawKey',
|
|
||||||
call: 'personal_importRawKey',
|
|
||||||
params: 2
|
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'sign',
|
|
||||||
call: 'personal_sign',
|
|
||||||
params: 3,
|
|
||||||
inputFormatter: [null, web3._extend.formatters.inputAddressFormatter, null]
|
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'ecRecover',
|
|
||||||
call: 'personal_ecRecover',
|
|
||||||
params: 2
|
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'openWallet',
|
|
||||||
call: 'personal_openWallet',
|
|
||||||
params: 2
|
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'deriveAccount',
|
|
||||||
call: 'personal_deriveAccount',
|
|
||||||
params: 3
|
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
|
||||||
name: 'signTransaction',
|
|
||||||
call: 'personal_signTransaction',
|
|
||||||
params: 2,
|
|
||||||
inputFormatter: [web3._extend.formatters.inputTransactionFormatter, null]
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
properties: [
|
|
||||||
new web3._extend.Property({
|
|
||||||
name: 'listWallets',
|
|
||||||
getter: 'personal_listWallets'
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
`
|
|
||||||
|
|
||||||
const RPC_JS = `
|
const RPC_JS = `
|
||||||
web3._extend({
|
web3._extend({
|
||||||
property: 'rpc',
|
property: 'rpc',
|
||||||
|
|
|
||||||
16
node/node.go
16
node/node.go
|
|
@ -378,25 +378,13 @@ func (n *Node) obtainJWTSecret(cliParam string) ([]byte, error) {
|
||||||
// startup. It's not meant to be called at any time afterwards as it makes certain
|
// startup. It's not meant to be called at any time afterwards as it makes certain
|
||||||
// assumptions about the state of the node.
|
// assumptions about the state of the node.
|
||||||
func (n *Node) startRPC() error {
|
func (n *Node) startRPC() error {
|
||||||
// Filter out personal api
|
if err := n.startInProc(n.rpcAPIs); err != nil {
|
||||||
var apis []rpc.API
|
|
||||||
for _, api := range n.rpcAPIs {
|
|
||||||
if api.Namespace == "personal" {
|
|
||||||
if n.config.EnablePersonal {
|
|
||||||
log.Warn("Deprecated personal namespace activated")
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apis = append(apis, api)
|
|
||||||
}
|
|
||||||
if err := n.startInProc(apis); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure IPC.
|
// Configure IPC.
|
||||||
if n.ipc.endpoint != "" {
|
if n.ipc.endpoint != "" {
|
||||||
if err := n.ipc.start(apis); err != nil {
|
if err := n.ipc.start(n.rpcAPIs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue