以太坊源码之『账户管理钱包分析』

如何实现通过geth生成以太坊地址?

personal.newAccount(password)

// 创建一个新的账号
func accountCreate(ctx *cli.Context) error {
	// 获取geth配置
	cfg := gethConfig{Node: defaultNodeConfig()}
	// 加载配置文件
	if file := ctx.GlobalString(configFileFlag.Name); file != "" {
		if err := loadConfig(file, &cfg); err != nil {
			utils.Fatalf("%v", err)
		}
	}
	// 设置节点相关协议配置
	utils.SetNodeConfig(ctx, &cfg.Node)
	// 获取账号配置
	scryptN, scryptP, keydir, err := cfg.Node.AccountConfig()

	if err != nil {
		utils.Fatalf("Failed to read configuration: %v", err)
	}
	// 解析用户密码
	password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
	// 通过密码获取一个新的地址,存储到keystore目录
	address, err := keystore.StoreKey(keydir, password, scryptN, scryptP)

	if err != nil {
		utils.Fatalf("Failed to create account: %v", err)
	}
	fmt.Printf("Address: {%x}\n", address)
	return nil
}

accounts源码分析:

在accounts包中,主要实现了以太坊客户端的钱包管理和账户管理,提供了两种钱包,分别是keystore,usb。同时以太坊合约代码的ABI也在accounts中实现。

key.go中

// 生成新的key结构
func newKey(rand io.Reader) (*Key, error) {
	// 通过ecdsa生成一对公私钥对
	privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
	if err != nil {
		return nil, err
	}
	// 通过ecdsa公钥生成key结构
	return newKeyFromECDSA(privateKeyECDSA), nil
}
// 生成地址,存储
func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) {
	// 通过给定的随机数生成新的key结构
	key, err := newKey(rand)
	if err != nil {
		return nil, accounts.Account{}, err
	}
	// 封装生成address
	a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}}
	// 存储私钥
	if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
		zeroKey(key.PrivateKey)
		return nil, a, err
	}
	return key, a, err
}

keystore_passphrase.go

accounts.go


package accounts

import (
	"math/big"

	ethereum "github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/event"
)

// 以太坊地址类型结构
type Account struct {
	// 20字节的数据
	Address common.Address `json:"address"` // Ethereum account address derived from the key
	URL     URL            `json:"url"`     // Optional resource locator within a backend
}

// 钱包接口
type Wallet interface {
	// 获取该钱包可以访问的规范路径
	URL() URL
	// 钱包状态
	Status() (string, error)
	// 初始化对钱包实例的访问
	Open(passphrase string) error
	// 释放open方法占用的资源
	Close() error
	// 账号列表
	Accounts() []Account
	// 查询指定账户是否属于该钱包
	Contains(account Account) bool
	// 专门给确定性钱包使用的
	Derive(path DerivationPath, pin bool) (Account, error)
	// 设置一个账户导出路径
	SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
	// 请求钱包为传入的哈希进行签名
	SignHash(account Account, hash []byte) ([]byte, error)
	// 请求钱包为传入的交易进行签名
	SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
	// 请求钱包使用指定 的passphrase为传入的哈希进行签名
	SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
	// 请求钱包使用指定 的passphrase为传入的交易进行签名
	SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
}

// 钱包的后端实现(服务),主要实现keystore钱包以及USB硬件钱包
type Backend interface {
	// 获取钱包列表
	Wallets() []Wallet
	// 订阅钱包相关事件
	Subscribe(sink chan<- WalletEvent) event.Subscription
}
// 钱包事件的类型
type WalletEventType int

const (
	// 在通过USB或者密码文件检测到新钱包的时候 ,会触发该事件
	WalletArrived WalletEventType = iota
	// 开户钱包所触发的事件
	WalletOpened
	WalletDropped
)
// 在检测到钱包账户发生改变时所触发的事件
type WalletEvent struct {
	Wallet Wallet          
	Kind   WalletEventType 
}

以太坊钱包管理器manager分析:

manager.go:



package accounts

import (
	"reflect"
	"sort"
	"sync"

	"github.com/ethereum/go-ethereum/event"
)
// 账户管理工具,可以和所有的backends进行通信来签名交易
type Manager struct {
	// 已注册的后台服务
	backends map[reflect.Type][]Backend
	// 钱包管理相关事件
	updaters []event.Subscription
	updates  chan WalletEvent
	// 已经注册过的钱包缓存
	wallets  []Wallet

	feed event.Feed

	quit chan chan error
	lock sync.RWMutex
}
// 新建管理器对象
func NewManager(backends ...Backend) *Manager {
	var wallets []Wallet
	for _, backend := range backends {
		// 调用所有backend的wallet方法,合并成完整的钱包列表
		wallets = merge(wallets, backend.Wallets()...)
	}
	updates := make(chan WalletEvent, 4*len(backends))

	subs := make([]event.Subscription, len(backends))
	for i, backend := range backends {
		// 注册update channel到后端服务中
		subs[i] = backend.Subscribe(updates)
	}
	// 封装
	am := &Manager{
		backends: make(map[reflect.Type][]Backend),
		updaters: subs,
		updates:  updates,
		wallets:  wallets,
		quit:     make(chan chan error),
	}
	for _, backend := range backends {
		kind := reflect.TypeOf(backend)
		am.backends[kind] = append(am.backends[kind], backend)
	}

	// 另起协程,监听钱包事件
	go am.update()

	return am
}
// 关闭账号管理器
func (am *Manager) Close() error {
	errc := make(chan error)
	am.quit <- errc
	return <-errc
}
// 钱包事件
func (am *Manager) update() {
	defer func() {
		am.lock.Lock()
		for _, sub := range am.updaters {
			sub.Unsubscribe()
		}
		am.updaters = nil
		am.lock.Unlock()
	}()
	// 循环监听钱包相关事件
	for {
		select {
		case event := <-am.updates:
			am.lock.Lock()
			switch event.Kind {
			// 判断事件类型
			case WalletArrived:
				am.wallets = merge(am.wallets, event.Wallet)
			case WalletDropped:
				am.wallets = drop(am.wallets, event.Wallet)
			}
			am.lock.Unlock()

			am.feed.Send(event)
		// 接收退出
		case errc := <-am.quit:
			errc <- nil
			return
		}
	}
}

// 返回指定的服务列表
func (am *Manager) Backends(kind reflect.Type) []Backend {
	return am.backends[kind]
}

// 返回该账号管理器下的所有签名账户
func (am *Manager) Wallets() []Wallet {
	am.lock.RLock()
	defer am.lock.RUnlock()

	cpy := make([]Wallet, len(am.wallets))
	copy(cpy, am.wallets)
	return cpy
}

// 通过URL查找指定的钱包
func (am *Manager) Wallet(url string) (Wallet, error) {
	am.lock.RLock()
	defer am.lock.RUnlock()

	parsed, err := parseURL(url)
	if err != nil {
		return nil, err
	}
	for _, wallet := range am.Wallets() {
		if wallet.URL() == parsed {
			return wallet, nil
		}
	}
	return nil, ErrUnknownWallet
}
// 通过指定的ACCOUNT查找钱包
func (am *Manager) Find(account Account) (Wallet, error) {
	am.lock.RLock()
	defer am.lock.RUnlock()

	for _, wallet := range am.wallets {
		if wallet.Contains(account) {
			return wallet, nil
		}
	}
	return nil, ErrUnknownAccount
}
// 订阅事件
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription {
	return am.feed.Subscribe(sink)
}
// 在指定的位置插入钱包,保证原来列表的顺序
func merge(slice []Wallet, wallets ...Wallet) []Wallet {
	for _, wallet := range wallets {
		n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
		if n == len(slice) {
			slice = append(slice, wallet)
			continue
		}
		slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...)
	}
	return slice
}
// 删除钱包列表中指定的钱包
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
	for _, wallet := range wallets {
		n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
		if n == len(slice) {
			continue
		}
		slice = append(slice[:n], slice[n+1:]...)
	}
	return slice
}

钱包列表获取与事件流程:

通过架构图,可以看到一个wallet中可以包含多个account,每个account中包含一个address结构(address, URL)

accounts中核心接口:

  • Backend接口:钱包后端,目前实现了keystore钱包和USB硬件钱包
  • Wallet接口:单个钱包,包含了一些打开 ,关闭,签名相关的接口函数
type KeyStore struct {
	// keyStore接口,用于访问账户关联的私钥
	storage  keyStore                     // Storage backend, might be cleartext or encrypted
	cache    *accountCache                // In-memory account cache over the filesystem storage
	changes  chan struct{}                // Channel receiving change notifications from the cache
	unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)

	// 所有钱包的集合,
	wallets     []accounts.Wallet       // Wallet wrappers around the individual key files
	updateFeed  event.Feed              // Event feed to notify wallet additions/removals
	updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
	updating    bool                    // Whether the event notification loop is running

	mu sync.RWMutex
}

type unlocked struct {
	*Key
	abort chan struct{}
}
// 概念指定的目录创建一个keystore
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
	keydir, _ = filepath.Abs(keydir)
	ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP}}
	ks.init(keydir)
	return ks
}
func NewPlaintextKeyStore(keydir string) *KeyStore {
	keydir, _ = filepath.Abs(keydir)
	ks := &KeyStore{storage: &keyStorePlain{keydir}}
	ks.init(keydir)
	return ks
}

func (ks *KeyStore) init(keydir string) {
	ks.mu.Lock()
	defer ks.mu.Unlock()
	ks.unlocked = make(map[common.Address]*unlocked)
	// 创建一个accountCache实例
	ks.cache, ks.changes = newAccountCache(keydir)
	runtime.SetFinalizer(ks, func(m *KeyStore) {
		m.cache.close()
	})
	// 获取当前的账号列表
	accs := ks.cache.accounts()
	ks.wallets = make([]accounts.Wallet, len(accs))
	for i := 0; i < len(accs); i++ {
		// 将keyStore填充到钱包中去
		ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
	}
}
// 通过keystore获取wallet列表
func (ks *KeyStore) Wallets() []accounts.Wallet {
	ks.refreshWallets()

	ks.mu.RLock()
	defer ks.mu.RUnlock()

	cpy := make([]accounts.Wallet, len(ks.wallets))
	copy(cpy, ks.wallets)
	return cpy
}

config.go


// 创建一个账号管理器
func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
	// 获取配送配置以及私钥存储的目录
	scryptN, scryptP, keydir, err := conf.AccountConfig()
	var ephemeral string
	if keydir == "" {
		keydir, err = ioutil.TempDir("", "go-ethereum-keystore")
		ephemeral = keydir
	}

	if err != nil {
		return nil, "", err
	}
	// 以700权限创建keystore目录,默认为datadir/keystore
	if err := os.MkdirAll(keydir, 0700); err != nil {
		return nil, "", err
	}
	// 初始化backend列表,创建keyStore实例
	backends := []accounts.Backend{
		keystore.NewKeyStore(keydir, scryptN, scryptP),
	}
	if !conf.NoUSB {
		if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
			log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
		} else {
			backends = append(backends, ledgerhub)
		}
		if trezorhub, err := usbwallet.NewTrezorHub(); err != nil {
			log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
		} else {
			backends = append(backends, trezorhub)
		}
	}
	// 在账户的列表初始化完成之后,通过NewManager函数创建一个账号管理器
	return accounts.NewManager(backends...), ephemeral, nil
}

钱包事件:

  • Manager中:updates字段,channel类型,主要用于接收钱包相关的事件。Manage需要调用backend的subscribe函数注册channel
  • Keystore:后端的事件,把注册请求转发给feed实例
  • Feed把channel记录下来
  • Manager可以能过该接口取消订阅

猜你喜欢

转载自blog.csdn.net/super_lixiang/article/details/83541134