use libevm: params, core/vm, eth/tracers/* + some of core/types (#1380)

* format: as coreth

* use libevm: params, core/vm, eth/tracers/* + some of core/types (#662)

Co-authored-by: Arran Schlosberg <[email protected]>

* format: as subnet-evm

* fix coreth formatting

* fix mocks

* fix contract deployer allowlist

* nits

* nits

* nit

* fix test import loop

* fix imports

* nits

* fix

* add TODO

---------

Co-authored-by: Arran Schlosberg <[email protected]>
This commit is contained in:
Darioush Jalali
2024-12-12 10:04:35 -08:00
committed by GitHub
co-authored by Arran Schlosberg
parent 7ce72518dd
commit e3a01fb149
339 changed files with 1756 additions and 39820 deletions
+3 -3
View File
@@ -33,10 +33,10 @@ import (
"io"
"math/big"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ava-labs/subnet-evm/accounts/external"
"github.com/ava-labs/subnet-evm/accounts/keystore"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/external"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
+5
View File
@@ -2179,6 +2179,11 @@ func golangBindings(t *testing.T, overload bool) {
if out, err := replacer.CombinedOutput(); err != nil {
t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out)
}
replacer = exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ethereum/[email protected]", "-replace", "github.com/ethereum/go-ethereum=github.com/ava-labs/[email protected]")
replacer.Dir = pkg
if out, err := replacer.CombinedOutput(); err != nil {
t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out)
}
tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.22")
tidier.Dir = pkg
if out, err := tidier.CombinedOutput(); err != nil {
@@ -452,7 +452,7 @@ var bindTests = []struct {
`"github.com/stretchr/testify/require"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/extstate"
"github.com/ava-labs/subnet-evm/precompile/allowlist"
`,
`
@@ -466,7 +466,7 @@ var bindTests = []struct {
require.Equal(t, testGreeting, unpackedGreeting)
// test that the allow list is generated correctly
stateDB := state.NewTestStateDB(t)
stateDB := extstate.NewTestStateDB(t)
address := common.BigToAddress(big.NewInt(1))
SetHelloWorldAllowListStatus(stateDB, address, allowlist.EnabledRole)
role := GetHelloWorldAllowListStatus(stateDB, address)
@@ -695,6 +695,11 @@ func TestPrecompileBind(t *testing.T) {
if out, err := replacer.CombinedOutput(); err != nil {
t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out)
}
replacer = exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ethereum/[email protected]", "-replace", "github.com/ethereum/go-ethereum=github.com/ava-labs/[email protected]")
replacer.Dir = pkg
if out, err := replacer.CombinedOutput(); err != nil {
t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out)
}
tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.22")
tidier.Dir = pkg
if out, err := tidier.CombinedOutput(); err != nil {
@@ -14,7 +14,7 @@ import (
"testing"
"math/big"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/extstate"
{{- if .Contract.AllowList}}
"github.com/ava-labs/subnet-evm/precompile/allowlist"
{{- end}}
@@ -187,12 +187,12 @@ func Test{{.Contract.Type}}Run(t *testing.T) {
// and runs them all together.
// Even if you don't add any custom tests, keep this. This will still
// run the default allowlist tests.
allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests)
allowlist.RunPrecompileWithAllowListTests(t, Module, extstate.NewTestStateDB, tests)
{{- else}}
// Run tests.
for name, test := range tests {
t.Run(name, func(t *testing.T) {
test.Run(t, Module, state.NewTestStateDB(t))
test.Run(t, Module, extstate.NewTestStateDB(t))
})
}
{{- end}}
@@ -247,12 +247,12 @@ func Benchmark{{.Contract.Type}}(b *testing.B) {
// and benchmarks them all together.
// Even if you don't add any custom tests, keep this. This will still
// run the default allowlist tests.
allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests)
allowlist.BenchPrecompileWithAllowList(b, Module, extstate.NewTestStateDB, tests)
{{- else}}
// Benchmark tests.
for name, test := range tests {
b.Run(name, func(b *testing.B) {
test.Bench(b, Module, state.NewTestStateDB(b))
test.Bench(b, Module, extstate.NewTestStateDB(b))
})
}
{{- end}}
-236
View File
@@ -1,236 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package accounts implements high level Ethereum account management.
package accounts
import (
"fmt"
"math/big"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/interfaces"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"golang.org/x/crypto/sha3"
)
// Account represents an Ethereum account located at a specific location defined
// by the optional URL field.
type Account struct {
Address common.Address `json:"address"` // Ethereum account address derived from the key
URL URL `json:"url"` // Optional resource locator within a backend
}
const (
MimetypeDataWithValidator = "data/validator"
MimetypeTypedData = "data/typed"
MimetypeClique = "application/x-clique-header"
MimetypeTextPlain = "text/plain"
)
// Wallet represents a software or hardware wallet that might contain one or more
// accounts (derived from the same seed).
type Wallet interface {
// URL retrieves the canonical path under which this wallet is reachable. It is
// used by upper layers to define a sorting order over all wallets from multiple
// backends.
URL() URL
// Status returns a textual status to aid the user in the current state of the
// wallet. It also returns an error indicating any failure the wallet might have
// encountered.
Status() (string, error)
// Open initializes access to a wallet instance. It is not meant to unlock or
// decrypt account keys, rather simply to establish a connection to hardware
// wallets and/or to access derivation seeds.
//
// The passphrase parameter may or may not be used by the implementation of a
// particular wallet instance. The reason there is no passwordless open method
// is to strive towards a uniform wallet handling, oblivious to the different
// backend providers.
//
// Please note, if you open a wallet, you must close it to release any allocated
// resources (especially important when working with hardware wallets).
Open(passphrase string) error
// Close releases any resources held by an open wallet instance.
Close() error
// Accounts retrieves the list of signing accounts the wallet is currently aware
// of. For hierarchical deterministic wallets, the list will not be exhaustive,
// rather only contain the accounts explicitly pinned during account derivation.
Accounts() []Account
// Contains returns whether an account is part of this particular wallet or not.
Contains(account Account) bool
// Derive attempts to explicitly derive a hierarchical deterministic account at
// the specified derivation path. If requested, the derived account will be added
// to the wallet's tracked account list.
Derive(path DerivationPath, pin bool) (Account, error)
// SelfDerive sets a base account derivation path from which the wallet attempts
// to discover non zero accounts and automatically add them to list of tracked
// accounts.
//
// Note, self derivation will increment the last component of the specified path
// opposed to descending into a child path to allow discovering accounts starting
// from non zero components.
//
// Some hardware wallets switched derivation paths through their evolution, so
// this method supports providing multiple bases to discover old user accounts
// too. Only the last base will be used to derive the next empty account.
//
// You can disable automatic account discovery by calling SelfDerive with a nil
// chain state reader.
SelfDerive(bases []DerivationPath, chain interfaces.ChainStateReader)
// SignData requests the wallet to sign the hash of the given data
// It looks up the account specified either solely via its address contained within,
// or optionally with the aid of any location metadata from the embedded URL field.
//
// If the wallet requires additional authentication to sign the request (e.g.
// a password to decrypt the account, or a PIN code to verify the transaction),
// an AuthNeededError instance will be returned, containing infos for the user
// about which fields or actions are needed. The user may retry by providing
// the needed details via SignDataWithPassphrase, or by other means (e.g. unlock
// the account in a keystore).
SignData(account Account, mimeType string, data []byte) ([]byte, error)
// SignDataWithPassphrase is identical to SignData, but also takes a password
// NOTE: there's a chance that an erroneous call might mistake the two strings, and
// supply password in the mimetype field, or vice versa. Thus, an implementation
// should never echo the mimetype or return the mimetype in the error-response
SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error)
// SignText requests the wallet to sign the hash of a given piece of data, prefixed
// by the Ethereum prefix scheme
// It looks up the account specified either solely via its address contained within,
// or optionally with the aid of any location metadata from the embedded URL field.
//
// If the wallet requires additional authentication to sign the request (e.g.
// a password to decrypt the account, or a PIN code to verify the transaction),
// an AuthNeededError instance will be returned, containing infos for the user
// about which fields or actions are needed. The user may retry by providing
// the needed details via SignTextWithPassphrase, or by other means (e.g. unlock
// the account in a keystore).
//
// This method should return the signature in 'canonical' format, with v 0 or 1.
SignText(account Account, text []byte) ([]byte, error)
// SignTextWithPassphrase is identical to Signtext, but also takes a password
SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
// SignTx requests the wallet to sign the given transaction.
//
// It looks up the account specified either solely via its address contained within,
// or optionally with the aid of any location metadata from the embedded URL field.
//
// If the wallet requires additional authentication to sign the request (e.g.
// a password to decrypt the account, or a PIN code to verify the transaction),
// an AuthNeededError instance will be returned, containing infos for the user
// about which fields or actions are needed. The user may retry by providing
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
// the account in a keystore).
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
// SignTxWithPassphrase is identical to SignTx, but also takes a password
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
}
// Backend is a "wallet provider" that may contain a batch of accounts they can
// sign transactions with and upon request, do so.
type Backend interface {
// Wallets retrieves the list of wallets the backend is currently aware of.
//
// The returned wallets are not opened by default. For software HD wallets this
// means that no base seeds are decrypted, and for hardware wallets that no actual
// connection is established.
//
// The resulting wallet list will be sorted alphabetically based on its internal
// URL assigned by the backend. Since wallets (especially hardware) may come and
// go, the same wallet might appear at a different positions in the list during
// subsequent retrievals.
Wallets() []Wallet
// Subscribe creates an async subscription to receive notifications when the
// backend detects the arrival or departure of a wallet.
Subscribe(sink chan<- WalletEvent) event.Subscription
}
// TextHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from.
//
// The hash is calculated as
//
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func TextHash(data []byte) []byte {
hash, _ := TextAndHash(data)
return hash
}
// TextAndHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from.
//
// The hash is calculated as
//
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func TextAndHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
hasher := sha3.NewLegacyKeccak256()
hasher.Write([]byte(msg))
return hasher.Sum(nil), msg
}
// WalletEventType represents the different event types that can be fired by
// the wallet subscription subsystem.
type WalletEventType int
const (
// WalletArrived is fired when a new wallet is detected either via USB or via
// a filesystem event in the keystore.
WalletArrived WalletEventType = iota
// WalletOpened is fired when a wallet is successfully opened with the purpose
// of starting any background processes such as automatic key derivation.
WalletOpened
// WalletDropped
WalletDropped
)
// WalletEvent is an event fired by an account backend when a wallet arrival or
// departure is detected.
type WalletEvent struct {
Wallet Wallet // Wallet instance arrived or departed
Kind WalletEventType // Event type that happened in the system
}
-43
View File
@@ -1,43 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func TestTextHash(t *testing.T) {
t.Parallel()
hash := TextHash([]byte("Hello Joe"))
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
if !bytes.Equal(hash, want) {
t.Fatalf("wrong hash: %x", hash)
}
}
-77
View File
@@ -1,77 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
import (
"errors"
"fmt"
)
// ErrUnknownAccount is returned for any requested operation for which no backend
// provides the specified account.
var ErrUnknownAccount = errors.New("unknown account")
// ErrUnknownWallet is returned for any requested operation for which no backend
// provides the specified wallet.
var ErrUnknownWallet = errors.New("unknown wallet")
// ErrNotSupported is returned when an operation is requested from an account
// backend that it does not support.
var ErrNotSupported = errors.New("not supported")
// ErrInvalidPassphrase is returned when a decryption operation receives a bad
// passphrase.
var ErrInvalidPassphrase = errors.New("invalid password")
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
// second time.
var ErrWalletAlreadyOpen = errors.New("wallet already open")
// ErrWalletClosed is returned if a wallet is offline.
var ErrWalletClosed = errors.New("wallet closed")
// AuthNeededError is returned by backends for signing requests where the user
// is required to provide further authentication before signing can succeed.
//
// This usually means either that a password needs to be supplied, or perhaps a
// one time PIN code displayed by some hardware device.
type AuthNeededError struct {
Needed string // Extra authentication the user needs to provide
}
// NewAuthNeededError creates a new authentication error with the extra details
// about the needed fields set.
func NewAuthNeededError(needed string) error {
return &AuthNeededError{
Needed: needed,
}
}
// Error implements the standard error interface.
func (err *AuthNeededError) Error() string {
return fmt.Sprintf("authentication needed: %s", err.Needed)
}
-280
View File
@@ -1,280 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package external
import (
"errors"
"fmt"
"math/big"
"sync"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/interfaces"
"github.com/ava-labs/subnet-evm/rpc"
"github.com/ava-labs/subnet-evm/signer/core/apitypes"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
)
type ExternalBackend struct {
signers []accounts.Wallet
}
func (eb *ExternalBackend) Wallets() []accounts.Wallet {
return eb.signers
}
func NewExternalBackend(endpoint string) (*ExternalBackend, error) {
signer, err := NewExternalSigner(endpoint)
if err != nil {
return nil, err
}
return &ExternalBackend{
signers: []accounts.Wallet{signer},
}, nil
}
func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
return event.NewSubscription(func(quit <-chan struct{}) error {
<-quit
return nil
})
}
// ExternalSigner provides an API to interact with an external signer (clef)
// It proxies request to the external signer while forwarding relevant
// request headers
type ExternalSigner struct {
client *rpc.Client
endpoint string
status string
cacheMu sync.RWMutex
cache []accounts.Account
}
func NewExternalSigner(endpoint string) (*ExternalSigner, error) {
client, err := rpc.Dial(endpoint)
if err != nil {
return nil, err
}
extsigner := &ExternalSigner{
client: client,
endpoint: endpoint,
}
// Check if reachable
version, err := extsigner.pingVersion()
if err != nil {
return nil, err
}
extsigner.status = fmt.Sprintf("ok [version=%v]", version)
return extsigner, nil
}
func (api *ExternalSigner) URL() accounts.URL {
return accounts.URL{
Scheme: "extapi",
Path: api.endpoint,
}
}
func (api *ExternalSigner) Status() (string, error) {
return api.status, nil
}
func (api *ExternalSigner) Open(passphrase string) error {
return errors.New("operation not supported on external signers")
}
func (api *ExternalSigner) Close() error {
return errors.New("operation not supported on external signers")
}
func (api *ExternalSigner) Accounts() []accounts.Account {
var accnts []accounts.Account
res, err := api.listAccounts()
if err != nil {
log.Error("account listing failed", "error", err)
return accnts
}
for _, addr := range res {
accnts = append(accnts, accounts.Account{
URL: accounts.URL{
Scheme: "extapi",
Path: api.endpoint,
},
Address: addr,
})
}
api.cacheMu.Lock()
api.cache = accnts
api.cacheMu.Unlock()
return accnts
}
func (api *ExternalSigner) Contains(account accounts.Account) bool {
api.cacheMu.RLock()
defer api.cacheMu.RUnlock()
if api.cache == nil {
// If we haven't already fetched the accounts, it's time to do so now
api.cacheMu.RUnlock()
api.Accounts()
api.cacheMu.RLock()
}
for _, a := range api.cache {
if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) {
return true
}
}
return false
}
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
return accounts.Account{}, errors.New("operation not supported on external signers")
}
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain interfaces.ChainStateReader) {
log.Error("operation SelfDerive not supported on external signers")
}
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
var res hexutil.Bytes
var signAddress = common.NewMixedcaseAddress(account.Address)
if err := api.client.Call(&res, "account_signData",
mimeType,
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
hexutil.Encode(data)); err != nil {
return nil, err
}
// If V is on 27/28-form, convert to 0/1 for Clique
if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) {
res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use
}
return res, nil
}
func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) {
var signature hexutil.Bytes
var signAddress = common.NewMixedcaseAddress(account.Address)
if err := api.client.Call(&signature, "account_signData",
accounts.MimetypeTextPlain,
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
hexutil.Encode(text)); err != nil {
return nil, err
}
if signature[64] == 27 || signature[64] == 28 {
// If clef is used as a backend, it may already have transformed
// the signature to ethereum-type signature.
signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1
}
return signature, nil
}
// signTransactionResult represents the signinig result returned by clef.
type signTransactionResult struct {
Raw hexutil.Bytes `json:"raw"`
Tx *types.Transaction `json:"tx"`
}
// SignTx sends the transaction to the external signer.
// If chainID is nil, or tx.ChainID is zero, the chain ID will be assigned
// by the external signer. For non-legacy transactions, the chain ID of the
// transaction overrides the chainID parameter.
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
data := hexutil.Bytes(tx.Data())
var to *common.MixedcaseAddress
if tx.To() != nil {
t := common.NewMixedcaseAddress(*tx.To())
to = &t
}
args := &apitypes.SendTxArgs{
Data: &data,
Nonce: hexutil.Uint64(tx.Nonce()),
Value: hexutil.Big(*tx.Value()),
Gas: hexutil.Uint64(tx.Gas()),
To: to,
From: common.NewMixedcaseAddress(account.Address),
}
switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType:
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
case types.DynamicFeeTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
default:
return nil, fmt.Errorf("unsupported tx type %d", tx.Type())
}
// We should request the default chain id that we're operating with
// (the chain we're executing on)
if chainID != nil && chainID.Sign() != 0 {
args.ChainID = (*hexutil.Big)(chainID)
}
if tx.Type() != types.LegacyTxType {
// However, if the user asked for a particular chain id, then we should
// use that instead.
if tx.ChainId().Sign() != 0 {
args.ChainID = (*hexutil.Big)(tx.ChainId())
}
accessList := tx.AccessList()
args.AccessList = &accessList
}
var res signTransactionResult
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
return nil, err
}
return res.Tx, nil
}
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
return []byte{}, errors.New("password-operations not supported on external signers")
}
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
return nil, errors.New("password-operations not supported on external signers")
}
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
return nil, errors.New("password-operations not supported on external signers")
}
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
var res []common.Address
if err := api.client.Call(&res, "account_list"); err != nil {
return nil, err
}
return res, nil
}
func (api *ExternalSigner) pingVersion() (string, error) {
var v string
if err := api.client.Call(&v, "account_version"); err != nil {
return "", err
}
return v, nil
}
-162
View File
@@ -1,162 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
import (
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"strings"
)
// DefaultRootDerivationPath is the root path to which custom derivation endpoints
// are appended. As such, the first account will be at m/44'/60'/0'/0, the second
// at m/44'/60'/0'/1, etc.
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
// at m/44'/60'/0'/0/1, etc.
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
// LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation
// endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the
// second at m/44'/60'/0'/1, etc.
var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
// DerivationPath represents the computer friendly version of a hierarchical
// deterministic wallet account derivation path.
//
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
// defines derivation paths to be of the form:
//
// m / purpose' / coin_type' / account' / change / address_index
//
// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
// the `coin_type` 60' (or 0x8000003C) to Ethereum.
//
// The root path for Ethereum is m/44'/60'/0'/0 according to the specification
// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
// yet whether accounts should increment the last component or the children of
// that. We will go with the simpler approach of incrementing the last component.
type DerivationPath []uint32
// ParseDerivationPath converts a user specified derivation path string to the
// internal binary representation.
//
// Full derivation paths need to start with the `m/` prefix, relative derivation
// paths (which will get appended to the default root path) must not have prefixes
// in front of the first element. Whitespace is ignored.
func ParseDerivationPath(path string) (DerivationPath, error) {
var result DerivationPath
// Handle absolute or relative paths
components := strings.Split(path, "/")
switch {
case len(components) == 0:
return nil, errors.New("empty derivation path")
case strings.TrimSpace(components[0]) == "":
return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")
case strings.TrimSpace(components[0]) == "m":
components = components[1:]
default:
result = append(result, DefaultRootDerivationPath...)
}
// All remaining components are relative, append one by one
if len(components) == 0 {
return nil, errors.New("empty derivation path") // Empty relative paths
}
for _, component := range components {
// Ignore any user added whitespace
component = strings.TrimSpace(component)
var value uint32
// Handle hardened paths
if strings.HasSuffix(component, "'") {
value = 0x80000000
component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
}
// Handle the non hardened component
bigval, ok := new(big.Int).SetString(component, 0)
if !ok {
return nil, fmt.Errorf("invalid component: %s", component)
}
max := math.MaxUint32 - value
if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 {
if value == 0 {
return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max)
}
return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max)
}
value += uint32(bigval.Uint64())
// Append and repeat
result = append(result, value)
}
return result, nil
}
// String implements the stringer interface, converting a binary derivation path
// to its canonical representation.
func (path DerivationPath) String() string {
result := "m"
for _, component := range path {
var hardened bool
if component >= 0x80000000 {
component -= 0x80000000
hardened = true
}
result = fmt.Sprintf("%s/%d", result, component)
if hardened {
result += "'"
}
}
return result
}
// MarshalJSON turns a derivation path into its json-serialized string
func (path DerivationPath) MarshalJSON() ([]byte, error) {
return json.Marshal(path.String())
}
// UnmarshalJSON a json-serialized string back into a derivation path
func (path *DerivationPath) UnmarshalJSON(b []byte) error {
var dp string
var err error
if err = json.Unmarshal(b, &dp); err != nil {
return err
}
*path, err = ParseDerivationPath(dp)
return err
}
-90
View File
@@ -1,90 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
import (
"reflect"
"testing"
)
// Tests that HD derivation paths can be correctly parsed into our internal binary
// representation.
func TestHDPathParsing(t *testing.T) {
t.Parallel()
tests := []struct {
input string
output DerivationPath
}{
// Plain absolute derivation paths
{"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
{"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
{"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
{"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
{"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
// Plain relative derivation paths
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
// Hexadecimal absolute derivation paths
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
{"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
{"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
{"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
{"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
// Hexadecimal relative derivation paths
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
// Weird inputs just to ensure they work
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
// Invalid derivation paths
{"", nil}, // Empty relative derivation path
{"m", nil}, // Empty absolute derivation path
{"m/", nil}, // Missing last derivation component
{"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error
{"m/2147483648'", nil}, // Overflows 32 bit integer
{"m/-1'", nil}, // Cannot contain negative number
}
for i, tt := range tests {
if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) {
t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output)
} else if path == nil && err == nil {
t.Errorf("test %d: nil path and error: %v", i, err)
}
}
}
-318
View File
@@ -1,318 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
"github.com/ava-labs/subnet-evm/accounts"
mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/exp/slices"
)
// Minimum amount of time between cache reloads. This limit applies if the platform does
// not support change notifications. It also applies if the keystore directory does not
// exist yet, the code will attempt to create a watcher at most this often.
const minReloadInterval = 2 * time.Second
// byURL defines the sorting order for accounts.
func byURL(a, b accounts.Account) int {
return a.URL.Cmp(b.URL)
}
// AmbiguousAddrError is returned when attempting to unlock
// an address for which more than one file exists.
type AmbiguousAddrError struct {
Addr common.Address
Matches []accounts.Account
}
func (err *AmbiguousAddrError) Error() string {
files := ""
for i, a := range err.Matches {
files += a.URL.Path
if i < len(err.Matches)-1 {
files += ", "
}
}
return fmt.Sprintf("multiple keys match address (%s)", files)
}
// accountCache is a live index of all accounts in the keystore.
type accountCache struct {
keydir string
watcher *watcher
mu sync.Mutex
all []accounts.Account
byAddr map[common.Address][]accounts.Account
throttle *time.Timer
notify chan struct{}
fileC fileCache
}
func newAccountCache(keydir string) (*accountCache, chan struct{}) {
ac := &accountCache{
keydir: keydir,
byAddr: make(map[common.Address][]accounts.Account),
notify: make(chan struct{}, 1),
fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()},
}
ac.watcher = newWatcher(ac)
return ac, ac.notify
}
func (ac *accountCache) accounts() []accounts.Account {
ac.maybeReload()
ac.mu.Lock()
defer ac.mu.Unlock()
cpy := make([]accounts.Account, len(ac.all))
copy(cpy, ac.all)
return cpy
}
func (ac *accountCache) hasAddress(addr common.Address) bool {
ac.maybeReload()
ac.mu.Lock()
defer ac.mu.Unlock()
return len(ac.byAddr[addr]) > 0
}
func (ac *accountCache) add(newAccount accounts.Account) {
ac.mu.Lock()
defer ac.mu.Unlock()
i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
if i < len(ac.all) && ac.all[i] == newAccount {
return
}
// newAccount is not in the cache.
ac.all = append(ac.all, accounts.Account{})
copy(ac.all[i+1:], ac.all[i:])
ac.all[i] = newAccount
ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
}
// note: removed needs to be unique here (i.e. both File and Address must be set).
func (ac *accountCache) delete(removed accounts.Account) {
ac.mu.Lock()
defer ac.mu.Unlock()
ac.all = removeAccount(ac.all, removed)
if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
delete(ac.byAddr, removed.Address)
} else {
ac.byAddr[removed.Address] = ba
}
}
// deleteByFile removes an account referenced by the given path.
func (ac *accountCache) deleteByFile(path string) {
ac.mu.Lock()
defer ac.mu.Unlock()
i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path })
if i < len(ac.all) && ac.all[i].URL.Path == path {
removed := ac.all[i]
ac.all = append(ac.all[:i], ac.all[i+1:]...)
if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
delete(ac.byAddr, removed.Address)
} else {
ac.byAddr[removed.Address] = ba
}
}
}
// watcherStarted returns true if the watcher loop started running (even if it
// has since also ended).
func (ac *accountCache) watcherStarted() bool {
ac.mu.Lock()
defer ac.mu.Unlock()
return ac.watcher.running || ac.watcher.runEnded
}
func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
for i := range slice {
if slice[i] == elem {
return append(slice[:i], slice[i+1:]...)
}
}
return slice
}
// find returns the cached account for address if there is a unique match.
// The exact matching rules are explained by the documentation of accounts.Account.
// Callers must hold ac.mu.
func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
// Limit search to address candidates if possible.
matches := ac.all
if (a.Address != common.Address{}) {
matches = ac.byAddr[a.Address]
}
if a.URL.Path != "" {
// If only the basename is specified, complete the path.
if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
}
for i := range matches {
if matches[i].URL == a.URL {
return matches[i], nil
}
}
if (a.Address == common.Address{}) {
return accounts.Account{}, ErrNoMatch
}
}
switch len(matches) {
case 1:
return matches[0], nil
case 0:
return accounts.Account{}, ErrNoMatch
default:
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
copy(err.Matches, matches)
slices.SortFunc(err.Matches, byURL)
return accounts.Account{}, err
}
}
func (ac *accountCache) maybeReload() {
ac.mu.Lock()
if ac.watcher.running {
ac.mu.Unlock()
return // A watcher is running and will keep the cache up-to-date.
}
if ac.throttle == nil {
ac.throttle = time.NewTimer(0)
} else {
select {
case <-ac.throttle.C:
default:
ac.mu.Unlock()
return // The cache was reloaded recently.
}
}
// No watcher running, start it.
ac.watcher.start()
ac.throttle.Reset(minReloadInterval)
ac.mu.Unlock()
ac.scanAccounts()
}
func (ac *accountCache) close() {
ac.mu.Lock()
ac.watcher.close()
if ac.throttle != nil {
ac.throttle.Stop()
}
if ac.notify != nil {
close(ac.notify)
ac.notify = nil
}
ac.mu.Unlock()
}
// scanAccounts checks if any changes have occurred on the filesystem, and
// updates the account cache accordingly
func (ac *accountCache) scanAccounts() error {
// Scan the entire folder metadata for file changes
creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
if err != nil {
log.Debug("Failed to reload keystore contents", "err", err)
return err
}
if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 {
return nil
}
// Create a helper method to scan the contents of the key files
var (
buf = new(bufio.Reader)
key struct {
Address string `json:"address"`
}
)
readAccount := func(path string) *accounts.Account {
fd, err := os.Open(path)
if err != nil {
log.Trace("Failed to open keystore file", "path", path, "err", err)
return nil
}
defer fd.Close()
buf.Reset(fd)
// Parse the address.
key.Address = ""
err = json.NewDecoder(buf).Decode(&key)
addr := common.HexToAddress(key.Address)
switch {
case err != nil:
log.Debug("Failed to decode keystore key", "path", path, "err", err)
case addr == common.Address{}:
log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
default:
return &accounts.Account{
Address: addr,
URL: accounts.URL{Scheme: KeyStoreScheme, Path: path},
}
}
return nil
}
// Process all the file diffs
start := time.Now()
for _, path := range creates.ToSlice() {
if a := readAccount(path); a != nil {
ac.add(*a)
}
}
for _, path := range deletes.ToSlice() {
ac.deleteByFile(path)
}
for _, path := range updates.ToSlice() {
ac.deleteByFile(path)
if a := readAccount(path); a != nil {
ac.add(*a)
}
}
end := time.Now()
select {
case ac.notify <- struct{}{}:
default:
}
log.Trace("Handled keystore changes", "time", end.Sub(start))
return nil
}
-419
View File
@@ -1,419 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/cespare/cp"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/slices"
)
var (
cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore"))
cachetestAccounts = []accounts.Account{
{
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
},
{
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
},
{
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
},
}
)
// waitWatcherStart waits up to 1s for the keystore watcher to start.
func waitWatcherStart(ks *KeyStore) bool {
// On systems where file watch is not supported, just return "ok".
if !ks.cache.watcher.enabled() {
return true
}
// The watcher should start, and then exit.
for t0 := time.Now(); time.Since(t0) < 1*time.Second; time.Sleep(100 * time.Millisecond) {
if ks.cache.watcherStarted() {
return true
}
}
return false
}
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
var list []accounts.Account
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
list = ks.Accounts()
if reflect.DeepEqual(list, wantAccounts) {
// ks should have also received change notifications
select {
case <-ks.changes:
default:
return errors.New("wasn't notified of new accounts")
}
return nil
}
}
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
}
func TestWatchNewFile(t *testing.T) {
t.Parallel()
dir, ks := tmpKeyStore(t, false)
// Ensure the watcher is started before adding any files.
ks.Accounts()
if !waitWatcherStart(ks) {
t.Fatal("keystore watcher didn't start in time")
}
// Move in the files.
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
for i := range cachetestAccounts {
wantAccounts[i] = accounts.Account{
Address: cachetestAccounts[i].Address,
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
}
if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
t.Fatal(err)
}
}
// ks should see the accounts.
if err := waitForAccounts(wantAccounts, ks); err != nil {
t.Error(err)
}
}
func TestWatchNoDir(t *testing.T) {
t.Parallel()
// Create ks but not the directory that it watches.
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int()))
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
list := ks.Accounts()
if len(list) > 0 {
t.Error("initial account list not empty:", list)
}
// The watcher should start, and then exit.
if !waitWatcherStart(ks) {
t.Fatal("keystore watcher didn't start in time")
}
// Create the directory and copy a key file into it.
os.MkdirAll(dir, 0700)
defer os.RemoveAll(dir)
file := filepath.Join(dir, "aaa")
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
t.Fatal(err)
}
// ks should see the account.
wantAccounts := []accounts.Account{cachetestAccounts[0]}
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
list = ks.Accounts()
if reflect.DeepEqual(list, wantAccounts) {
// ks should have also received change notifications
select {
case <-ks.changes:
default:
t.Fatalf("wasn't notified of new accounts")
}
return
}
time.Sleep(d)
}
t.Errorf("\ngot %v\nwant %v", list, wantAccounts)
}
func TestCacheInitialReload(t *testing.T) {
t.Parallel()
cache, _ := newAccountCache(cachetestDir)
accounts := cache.accounts()
if !reflect.DeepEqual(accounts, cachetestAccounts) {
t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
}
}
func TestCacheAddDeleteOrder(t *testing.T) {
t.Parallel()
cache, _ := newAccountCache("testdata/no-such-dir")
cache.watcher.running = true // prevent unexpected reloads
accs := []accounts.Account{
{
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
},
{
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
},
{
Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
},
{
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
},
{
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
},
{
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
},
{
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
},
}
for _, a := range accs {
cache.add(a)
}
// Add some of them twice to check that they don't get reinserted.
cache.add(accs[0])
cache.add(accs[2])
// Check that the account list is sorted by filename.
wantAccounts := make([]accounts.Account, len(accs))
copy(wantAccounts, accs)
slices.SortFunc(wantAccounts, byURL)
list := cache.accounts()
if !reflect.DeepEqual(list, wantAccounts) {
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
}
for _, a := range accs {
if !cache.hasAddress(a.Address) {
t.Errorf("expected hasAccount(%x) to return true", a.Address)
}
}
if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
}
// Delete a few keys from the cache.
for i := 0; i < len(accs); i += 2 {
cache.delete(wantAccounts[i])
}
cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
// Check content again after deletion.
wantAccountsAfterDelete := []accounts.Account{
wantAccounts[1],
wantAccounts[3],
wantAccounts[5],
}
list = cache.accounts()
if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
}
for _, a := range wantAccountsAfterDelete {
if !cache.hasAddress(a.Address) {
t.Errorf("expected hasAccount(%x) to return true", a.Address)
}
}
if cache.hasAddress(wantAccounts[0].Address) {
t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
}
}
func TestCacheFind(t *testing.T) {
t.Parallel()
dir := filepath.Join("testdata", "dir")
cache, _ := newAccountCache(dir)
cache.watcher.running = true // prevent unexpected reloads
accs := []accounts.Account{
{
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
},
{
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
},
{
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
},
{
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
},
}
for _, a := range accs {
cache.add(a)
}
nomatchAccount := accounts.Account{
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
}
tests := []struct {
Query accounts.Account
WantResult accounts.Account
WantError error
}{
// by address
{Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
// by file
{Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
// by basename
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
// by file and address
{Query: accs[0], WantResult: accs[0]},
// ambiguous address, tie resolved by file
{Query: accs[2], WantResult: accs[2]},
// ambiguous address error
{
Query: accounts.Account{Address: accs[2].Address},
WantError: &AmbiguousAddrError{
Addr: accs[2].Address,
Matches: []accounts.Account{accs[2], accs[3]},
},
},
// no match error
{Query: nomatchAccount, WantError: ErrNoMatch},
{Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
{Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
}
for i, test := range tests {
a, err := cache.find(test.Query)
if !reflect.DeepEqual(err, test.WantError) {
t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
continue
}
if a != test.WantResult {
t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
continue
}
}
}
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
// is noticed by the watcher, and the account cache is updated accordingly
func TestUpdatedKeyfileContents(t *testing.T) {
t.Parallel()
// Create a temporary keystore to test with
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
list := ks.Accounts()
if len(list) > 0 {
t.Error("initial account list not empty:", list)
}
if !waitWatcherStart(ks) {
t.Fatal("keystore watcher didn't start in time")
}
// Create the directory and copy a key file into it.
os.MkdirAll(dir, 0700)
defer os.RemoveAll(dir)
file := filepath.Join(dir, "aaa")
// Place one of our testfiles in there
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
t.Fatal(err)
}
// ks should see the account.
wantAccounts := []accounts.Account{cachetestAccounts[0]}
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
if err := waitForAccounts(wantAccounts, ks); err != nil {
t.Error(err)
return
}
// needed so that modTime of `file` will be greater than its current value after forceCopyFile
time.Sleep(time.Second)
// Now replace file contents
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
t.Fatal(err)
return
}
wantAccounts = []accounts.Account{cachetestAccounts[1]}
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
if err := waitForAccounts(wantAccounts, ks); err != nil {
t.Errorf("First replacement failed")
t.Error(err)
return
}
// needed so that modTime of `file` will be greater than its current value after forceCopyFile
time.Sleep(time.Second)
// Now replace file contents again
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
t.Fatal(err)
return
}
wantAccounts = []accounts.Account{cachetestAccounts[2]}
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
if err := waitForAccounts(wantAccounts, ks); err != nil {
t.Errorf("Second replacement failed")
t.Error(err)
return
}
// needed so that modTime of `file` will be greater than its current value after os.WriteFile
time.Sleep(time.Second)
// Now replace file contents with crap
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {
t.Fatal(err)
return
}
if err := waitForAccounts([]accounts.Account{}, ks); err != nil {
t.Errorf("Emptying account file failed")
t.Error(err)
return
}
}
// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
func forceCopyFile(dst, src string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, data, 0644)
}
-115
View File
@@ -1,115 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"os"
"path/filepath"
"strings"
"sync"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/log"
)
// fileCache is a cache of files seen during scan of keystore.
type fileCache struct {
all mapset.Set[string] // Set of all files from the keystore folder
lastMod time.Time // Last time instance when a file was modified
mu sync.Mutex
}
// scan performs a new scan on the given directory, compares against the already
// cached filenames, and returns file sets: creates, deletes, updates.
func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string], mapset.Set[string], error) {
t0 := time.Now()
// List all the files from the keystore folder
files, err := os.ReadDir(keyDir)
if err != nil {
return nil, nil, nil, err
}
t1 := time.Now()
fc.mu.Lock()
defer fc.mu.Unlock()
// Iterate all the files and gather their metadata
all := mapset.NewThreadUnsafeSet[string]()
mods := mapset.NewThreadUnsafeSet[string]()
var newLastMod time.Time
for _, fi := range files {
path := filepath.Join(keyDir, fi.Name())
// Skip any non-key files from the folder
if nonKeyFile(fi) {
log.Trace("Ignoring file on account scan", "path", path)
continue
}
// Gather the set of all and freshly modified files
all.Add(path)
info, err := fi.Info()
if err != nil {
return nil, nil, nil, err
}
modified := info.ModTime()
if modified.After(fc.lastMod) {
mods.Add(path)
}
if modified.After(newLastMod) {
newLastMod = modified
}
}
t2 := time.Now()
// Update the tracked files and return the three sets
deletes := fc.all.Difference(all) // Deletes = previous - current
creates := all.Difference(fc.all) // Creates = current - previous
updates := mods.Difference(creates) // Updates = modified - creates
fc.all, fc.lastMod = all, newLastMod
t3 := time.Now()
// Report on the scanning stats and return
log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2))
return creates, deletes, updates, nil
}
// nonKeyFile ignores editor backups, hidden files and folders/symlinks.
func nonKeyFile(fi os.DirEntry) bool {
// Skip editor backups and UNIX-style hidden files.
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
return true
}
// Skip misc special files, directories (yes, symlinks too).
if fi.IsDir() || !fi.Type().IsRegular() {
return true
}
return false
}
-251
View File
@@ -1,251 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/google/uuid"
)
const (
version = 3
)
type Key struct {
Id uuid.UUID // Version 4 "random" for unique id not derived from key data
// to simplify lookups we also store the address
Address common.Address
// we only store privkey as pubkey/address can be derived from it
// privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey
}
type keyStore interface {
// Loads and decrypts the key from disk.
GetKey(addr common.Address, filename string, auth string) (*Key, error)
// Writes and encrypts the key.
StoreKey(filename string, k *Key, auth string) error
// Joins filename with the key directory unless it is already absolute.
JoinPath(filename string) string
}
type plainKeyJSON struct {
Address string `json:"address"`
PrivateKey string `json:"privatekey"`
Id string `json:"id"`
Version int `json:"version"`
}
type encryptedKeyJSONV3 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version int `json:"version"`
}
type encryptedKeyJSONV1 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version string `json:"version"`
}
type CryptoJSON struct {
Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"`
CipherParams cipherparamsJSON `json:"cipherparams"`
KDF string `json:"kdf"`
KDFParams map[string]interface{} `json:"kdfparams"`
MAC string `json:"mac"`
}
type cipherparamsJSON struct {
IV string `json:"iv"`
}
func (k *Key) MarshalJSON() (j []byte, err error) {
jStruct := plainKeyJSON{
hex.EncodeToString(k.Address[:]),
hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)),
k.Id.String(),
version,
}
j, err = json.Marshal(jStruct)
return j, err
}
func (k *Key) UnmarshalJSON(j []byte) (err error) {
keyJSON := new(plainKeyJSON)
err = json.Unmarshal(j, &keyJSON)
if err != nil {
return err
}
u := new(uuid.UUID)
*u, err = uuid.Parse(keyJSON.Id)
if err != nil {
return err
}
k.Id = *u
addr, err := hex.DecodeString(keyJSON.Address)
if err != nil {
return err
}
privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey)
if err != nil {
return err
}
k.Address = common.BytesToAddress(addr)
k.PrivateKey = privkey
return nil
}
func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
return newKeyFromECDSA(privateKeyECDSA)
}
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
id, err := uuid.NewRandom()
if err != nil {
panic(fmt.Sprintf("Could not create random uuid: %v", err))
}
key := &Key{
Id: id,
Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
PrivateKey: privateKeyECDSA,
}
return key
}
// NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit
// into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we
// retry until the first byte is 0.
func NewKeyForDirectICAP(rand io.Reader) *Key {
randBytes := make([]byte, 64)
_, err := rand.Read(randBytes)
if err != nil {
panic("key generation: could not read from random source: " + err.Error())
}
reader := bytes.NewReader(randBytes)
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader)
if err != nil {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
}
key := newKeyFromECDSA(privateKeyECDSA)
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
return NewKeyForDirectICAP(rand)
}
return key
}
func NewKey(rand io.Reader) (*Key, error) {
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
if err != nil {
return nil, err
}
return newKeyFromECDSA(privateKeyECDSA), nil
}
func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) {
key, err := NewKey(rand)
if err != nil {
return nil, accounts.Account{}, err
}
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
}
func writeTemporaryKeyFile(file string, content []byte) (string, error) {
// Create the keystore directory with appropriate permissions
// in case it is not present yet.
const dirPerm = 0700
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
return "", err
}
// Atomic write: create a temporary hidden file first
// then move it into place. TempFile assigns mode 0600.
f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
if err != nil {
return "", err
}
if _, err := f.Write(content); err != nil {
f.Close()
os.Remove(f.Name())
return "", err
}
f.Close()
return f.Name(), nil
}
func writeKeyFile(file string, content []byte) error {
name, err := writeTemporaryKeyFile(file, content)
if err != nil {
return err
}
return os.Rename(name, file)
}
// keyFileName implements the naming convention for keyfiles:
// UTC--<created_at UTC ISO8601>-<address hex>
func keyFileName(keyAddr common.Address) string {
ts := time.Now().UTC()
return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:]))
}
func toISO8601(t time.Time) string {
var tz string
name, offset := t.Zone()
if name == "UTC" {
tz = "Z"
} else {
tz = fmt.Sprintf("%03d00", offset/3600)
}
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s",
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
}
-525
View File
@@ -1,525 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package keystore implements encrypted storage of secp256k1 private keys.
//
// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
package keystore
import (
"crypto/ecdsa"
crand "crypto/rand"
"errors"
"math/big"
"os"
"path/filepath"
"reflect"
"runtime"
"sync"
"time"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
)
var (
ErrLocked = accounts.NewAuthNeededError("password or unlock")
ErrNoMatch = errors.New("no key for given address or file")
ErrDecrypt = errors.New("could not decrypt key with given password")
// ErrAccountAlreadyExists is returned if an account attempted to import is
// already present in the keystore.
ErrAccountAlreadyExists = errors.New("account already exists")
)
// KeyStoreType is the reflect type of a keystore backend.
var KeyStoreType = reflect.TypeOf(&KeyStore{})
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
const KeyStoreScheme = "keystore"
// Maximum time between wallet refreshes (if filesystem notifications don't work).
const walletRefreshCycle = 3 * time.Second
// KeyStore manages a key storage directory on disk.
type KeyStore struct {
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
importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing
}
type unlocked struct {
*Key
abort chan struct{}
}
// NewKeyStore creates a keystore for the given directory.
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
keydir, _ = filepath.Abs(keydir)
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
ks.init(keydir)
return ks
}
// NewPlaintextKeyStore creates a keystore for the given directory.
// Deprecated: Use NewKeyStore.
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) {
// Lock the mutex since the account cache might call back with events
ks.mu.Lock()
defer ks.mu.Unlock()
// Initialize the set of unlocked keys and the account cache
ks.unlocked = make(map[common.Address]*unlocked)
ks.cache, ks.changes = newAccountCache(keydir)
// TODO: In order for this finalizer to work, there must be no references
// to ks. addressCache doesn't keep a reference but unlocked keys do,
// so the finalizer will not trigger until all timed unlocks have expired.
runtime.SetFinalizer(ks, func(m *KeyStore) {
m.cache.close()
})
// Create the initial list of wallets from the cache
accs := ks.cache.accounts()
ks.wallets = make([]accounts.Wallet, len(accs))
for i := 0; i < len(accs); i++ {
ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
}
}
// Wallets implements accounts.Backend, returning all single-key wallets from the
// keystore directory.
func (ks *KeyStore) Wallets() []accounts.Wallet {
// Make sure the list of wallets is in sync with the account cache
ks.refreshWallets()
ks.mu.RLock()
defer ks.mu.RUnlock()
cpy := make([]accounts.Wallet, len(ks.wallets))
copy(cpy, ks.wallets)
return cpy
}
// refreshWallets retrieves the current account list and based on that does any
// necessary wallet refreshes.
func (ks *KeyStore) refreshWallets() {
// Retrieve the current list of accounts
ks.mu.Lock()
accs := ks.cache.accounts()
// Transform the current list of wallets into the new one
var (
wallets = make([]accounts.Wallet, 0, len(accs))
events []accounts.WalletEvent
)
for _, account := range accs {
// Drop wallets while they were in front of the next account
for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
ks.wallets = ks.wallets[1:]
}
// If there are no more wallets or the account is before the next, wrap new wallet
if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
wallet := &keystoreWallet{account: account, keystore: ks}
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
wallets = append(wallets, wallet)
continue
}
// If the account is the same as the first wallet, keep it
if ks.wallets[0].Accounts()[0] == account {
wallets = append(wallets, ks.wallets[0])
ks.wallets = ks.wallets[1:]
continue
}
}
// Drop any leftover wallets and set the new batch
for _, wallet := range ks.wallets {
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
}
ks.wallets = wallets
ks.mu.Unlock()
// Fire all wallet events and return
for _, event := range events {
ks.updateFeed.Send(event)
}
}
// Subscribe implements accounts.Backend, creating an async subscription to
// receive notifications on the addition or removal of keystore wallets.
func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
// We need the mutex to reliably start/stop the update loop
ks.mu.Lock()
defer ks.mu.Unlock()
// Subscribe the caller and track the subscriber count
sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink))
// Subscribers require an active notification loop, start it
if !ks.updating {
ks.updating = true
go ks.updater()
}
return sub
}
// updater is responsible for maintaining an up-to-date list of wallets stored in
// the keystore, and for firing wallet addition/removal events. It listens for
// account change events from the underlying account cache, and also periodically
// forces a manual refresh (only triggers for systems where the filesystem notifier
// is not running).
func (ks *KeyStore) updater() {
for {
// Wait for an account update or a refresh timeout
select {
case <-ks.changes:
case <-time.After(walletRefreshCycle):
}
// Run the wallet refresher
ks.refreshWallets()
// If all our subscribers left, stop the updater
ks.mu.Lock()
if ks.updateScope.Count() == 0 {
ks.updating = false
ks.mu.Unlock()
return
}
ks.mu.Unlock()
}
}
// HasAddress reports whether a key with the given address is present.
func (ks *KeyStore) HasAddress(addr common.Address) bool {
return ks.cache.hasAddress(addr)
}
// Accounts returns all key files present in the directory.
func (ks *KeyStore) Accounts() []accounts.Account {
return ks.cache.accounts()
}
// Delete deletes the key matched by account if the passphrase is correct.
// If the account contains no filename, the address must match a unique key.
func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
// Decrypting the key isn't really necessary, but we do
// it anyway to check the password and zero out the key
// immediately afterwards.
a, key, err := ks.getDecryptedKey(a, passphrase)
if key != nil {
zeroKey(key.PrivateKey)
}
if err != nil {
return err
}
// The order is crucial here. The key is dropped from the
// cache after the file is gone so that a reload happening in
// between won't insert it into the cache again.
err = os.Remove(a.URL.Path)
if err == nil {
ks.cache.delete(a)
ks.refreshWallets()
}
return err
}
// SignHash calculates a ECDSA signature for the given hash. The produced
// signature is in the [R || S || V] format where V is 0 or 1.
func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
// Look up the key to sign with and abort if it cannot be found
ks.mu.RLock()
defer ks.mu.RUnlock()
unlockedKey, found := ks.unlocked[a.Address]
if !found {
return nil, ErrLocked
}
// Sign the hash using plain ECDSA operations
return crypto.Sign(hash, unlockedKey.PrivateKey)
}
// SignTx signs the given transaction with the requested account.
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// Look up the key to sign with and abort if it cannot be found
ks.mu.RLock()
defer ks.mu.RUnlock()
unlockedKey, found := ks.unlocked[a.Address]
if !found {
return nil, ErrLocked
}
// Depending on the presence of the chain ID, sign with 2718 or homestead
signer := types.LatestSignerForChainID(chainID)
return types.SignTx(tx, signer, unlockedKey.PrivateKey)
}
// SignHashWithPassphrase signs hash if the private key matching the given address
// can be decrypted with the given passphrase. The produced signature is in the
// [R || S || V] format where V is 0 or 1.
func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
_, key, err := ks.getDecryptedKey(a, passphrase)
if err != nil {
return nil, err
}
defer zeroKey(key.PrivateKey)
return crypto.Sign(hash, key.PrivateKey)
}
// SignTxWithPassphrase signs the transaction if the private key matching the
// given address can be decrypted with the given passphrase.
func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
_, key, err := ks.getDecryptedKey(a, passphrase)
if err != nil {
return nil, err
}
defer zeroKey(key.PrivateKey)
// Depending on the presence of the chain ID, sign with or without replay protection.
signer := types.LatestSignerForChainID(chainID)
return types.SignTx(tx, signer, key.PrivateKey)
}
// Unlock unlocks the given account indefinitely.
func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
return ks.TimedUnlock(a, passphrase, 0)
}
// Lock removes the private key with the given address from memory.
func (ks *KeyStore) Lock(addr common.Address) error {
ks.mu.Lock()
if unl, found := ks.unlocked[addr]; found {
ks.mu.Unlock()
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
} else {
ks.mu.Unlock()
}
return nil
}
// TimedUnlock unlocks the given account with the passphrase. The account
// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
// until the program exits. The account must match a unique key file.
//
// If the account address is already unlocked for a duration, TimedUnlock extends or
// shortens the active unlock timeout. If the address was previously unlocked
// indefinitely the timeout is not altered.
func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
a, key, err := ks.getDecryptedKey(a, passphrase)
if err != nil {
return err
}
ks.mu.Lock()
defer ks.mu.Unlock()
u, found := ks.unlocked[a.Address]
if found {
if u.abort == nil {
// The address was unlocked indefinitely, so unlocking
// it with a timeout would be confusing.
zeroKey(key.PrivateKey)
return nil
}
// Terminate the expire goroutine and replace it below.
close(u.abort)
}
if timeout > 0 {
u = &unlocked{Key: key, abort: make(chan struct{})}
go ks.expire(a.Address, u, timeout)
} else {
u = &unlocked{Key: key}
}
ks.unlocked[a.Address] = u
return nil
}
// Find resolves the given account into a unique entry in the keystore.
func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) {
ks.cache.maybeReload()
ks.cache.mu.Lock()
a, err := ks.cache.find(a)
ks.cache.mu.Unlock()
return a, err
}
func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) {
a, err := ks.Find(a)
if err != nil {
return a, nil, err
}
key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
return a, key, err
}
func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) {
t := time.NewTimer(timeout)
defer t.Stop()
select {
case <-u.abort:
// just quit
case <-t.C:
ks.mu.Lock()
// only drop if it's still the same key instance that dropLater
// was launched with. we can check that using pointer equality
// because the map stores a new pointer every time the key is
// unlocked.
if ks.unlocked[addr] == u {
zeroKey(u.PrivateKey)
delete(ks.unlocked, addr)
}
ks.mu.Unlock()
}
}
// NewAccount generates a new key and stores it into the key directory,
// encrypting it with the passphrase.
func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) {
_, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
if err != nil {
return accounts.Account{}, err
}
// Add the account to the cache immediately rather
// than waiting for file system notifications to pick it up.
ks.cache.add(account)
ks.refreshWallets()
return account, nil
}
// Export exports as a JSON key, encrypted with newPassphrase.
func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
_, key, err := ks.getDecryptedKey(a, passphrase)
if err != nil {
return nil, err
}
var N, P int
if store, ok := ks.storage.(*keyStorePassphrase); ok {
N, P = store.scryptN, store.scryptP
} else {
N, P = StandardScryptN, StandardScryptP
}
return EncryptKey(key, newPassphrase, N, P)
}
// Import stores the given encrypted JSON key into the key directory.
func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) {
key, err := DecryptKey(keyJSON, passphrase)
if key != nil && key.PrivateKey != nil {
defer zeroKey(key.PrivateKey)
}
if err != nil {
return accounts.Account{}, err
}
ks.importMu.Lock()
defer ks.importMu.Unlock()
if ks.cache.hasAddress(key.Address) {
return accounts.Account{
Address: key.Address,
}, ErrAccountAlreadyExists
}
return ks.importKey(key, newPassphrase)
}
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
ks.importMu.Lock()
defer ks.importMu.Unlock()
key := newKeyFromECDSA(priv)
if ks.cache.hasAddress(key.Address) {
return accounts.Account{
Address: key.Address,
}, ErrAccountAlreadyExists
}
return ks.importKey(key, passphrase)
}
func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) {
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
return accounts.Account{}, err
}
ks.cache.add(a)
ks.refreshWallets()
return a, nil
}
// Update changes the passphrase of an existing account.
func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error {
a, key, err := ks.getDecryptedKey(a, passphrase)
if err != nil {
return err
}
return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
}
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
// a key file in the key directory. The key file is encrypted with the same passphrase.
func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) {
a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase)
if err != nil {
return a, err
}
ks.cache.add(a)
ks.refreshWallets()
return a, nil
}
// isUpdating returns whether the event notification loop is running.
// This method is mainly meant for tests.
func (ks *KeyStore) isUpdating() bool {
ks.mu.RLock()
defer ks.mu.RUnlock()
return ks.updating
}
// zeroKey zeroes a private key in memory.
func zeroKey(k *ecdsa.PrivateKey) {
b := k.D.Bits()
for i := range b {
b[i] = 0
}
}
-477
View File
@@ -1,477 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"math/rand"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"golang.org/x/exp/slices"
)
var testSigData = make([]byte, 32)
func TestKeyStore(t *testing.T) {
t.Parallel()
dir, ks := tmpKeyStore(t, true)
a, err := ks.NewAccount("foo")
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(a.URL.Path, dir) {
t.Errorf("account file %s doesn't have dir prefix", a.URL)
}
stat, err := os.Stat(a.URL.Path)
if err != nil {
t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
}
if runtime.GOOS != "windows" && stat.Mode() != 0600 {
t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600)
}
if !ks.HasAddress(a.Address) {
t.Errorf("HasAccount(%x) should've returned true", a.Address)
}
if err := ks.Update(a, "foo", "bar"); err != nil {
t.Errorf("Update error: %v", err)
}
if err := ks.Delete(a, "bar"); err != nil {
t.Errorf("Delete error: %v", err)
}
if common.FileExist(a.URL.Path) {
t.Errorf("account file %s should be gone after Delete", a.URL)
}
if ks.HasAddress(a.Address) {
t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address)
}
}
func TestSign(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
pass := "" // not used but required by API
a1, err := ks.NewAccount(pass)
if err != nil {
t.Fatal(err)
}
if err := ks.Unlock(a1, ""); err != nil {
t.Fatal(err)
}
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil {
t.Fatal(err)
}
}
func TestSignWithPassphrase(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
pass := "passwd"
acc, err := ks.NewAccount(pass)
if err != nil {
t.Fatal(err)
}
if _, unlocked := ks.unlocked[acc.Address]; unlocked {
t.Fatal("expected account to be locked")
}
_, err = ks.SignHashWithPassphrase(acc, pass, testSigData)
if err != nil {
t.Fatal(err)
}
if _, unlocked := ks.unlocked[acc.Address]; unlocked {
t.Fatal("expected account to be locked")
}
if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil {
t.Fatal("expected SignHashWithPassphrase to fail with invalid password")
}
}
func TestTimedUnlock(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
pass := "foo"
a1, err := ks.NewAccount(pass)
if err != nil {
t.Fatal(err)
}
// Signing without passphrase fails because account is locked
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
if err != ErrLocked {
t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
}
// Signing with passphrase works
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
t.Fatal(err)
}
// Signing without passphrase works because account is temp unlocked
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
if err != nil {
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
}
// Signing fails again after automatic locking
time.Sleep(250 * time.Millisecond)
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
if err != ErrLocked {
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
}
}
func TestOverrideUnlock(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, false)
pass := "foo"
a1, err := ks.NewAccount(pass)
if err != nil {
t.Fatal(err)
}
// Unlock indefinitely.
if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil {
t.Fatal(err)
}
// Signing without passphrase works because account is temp unlocked
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
if err != nil {
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
}
// reset unlock to a shorter period, invalidates the previous unlock
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
t.Fatal(err)
}
// Signing without passphrase still works because account is temp unlocked
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
if err != nil {
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
}
// Signing fails again after automatic locking
time.Sleep(250 * time.Millisecond)
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
if err != ErrLocked {
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
}
}
// This test should fail under -race if signing races the expiration goroutine.
func TestSignRace(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, false)
// Create a test account.
a1, err := ks.NewAccount("")
if err != nil {
t.Fatal("could not create the test account", err)
}
if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil {
t.Fatal("could not unlock the test account", err)
}
end := time.Now().Add(500 * time.Millisecond)
for time.Now().Before(end) {
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked {
return
} else if err != nil {
t.Errorf("Sign error: %v", err)
return
}
time.Sleep(1 * time.Millisecond)
}
t.Errorf("Account did not lock within the timeout")
}
// waitForKsUpdating waits until the updating-status of the ks reaches the
// desired wantStatus.
// It waits for a maximum time of maxTime, and returns false if it does not
// finish in time
func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time.Duration) bool {
t.Helper()
// Wait max 250 ms, then return false
for t0 := time.Now(); time.Since(t0) < maxTime; {
if ks.isUpdating() == wantStatus {
return true
}
time.Sleep(25 * time.Millisecond)
}
return false
}
// Tests that the wallet notifier loop starts and stops correctly based on the
// addition and removal of wallet event subscriptions.
func TestWalletNotifierLifecycle(t *testing.T) {
t.Parallel()
// Create a temporary keystore to test with
_, ks := tmpKeyStore(t, false)
// Ensure that the notification updater is not running yet
time.Sleep(250 * time.Millisecond)
if ks.isUpdating() {
t.Errorf("wallet notifier running without subscribers")
}
// Subscribe to the wallet feed and ensure the updater boots up
updates := make(chan accounts.WalletEvent)
subs := make([]event.Subscription, 2)
for i := 0; i < len(subs); i++ {
// Create a new subscription
subs[i] = ks.Subscribe(updates)
if !waitForKsUpdating(t, ks, true, 250*time.Millisecond) {
t.Errorf("sub %d: wallet notifier not running after subscription", i)
}
}
// Close all but one sub
for i := 0; i < len(subs)-1; i++ {
// Close an existing subscription
subs[i].Unsubscribe()
}
// Check that it is still running
time.Sleep(250 * time.Millisecond)
if !ks.isUpdating() {
t.Fatal("event notifier stopped prematurely")
}
// Unsubscribe the last one and ensure the updater terminates eventually.
subs[len(subs)-1].Unsubscribe()
if !waitForKsUpdating(t, ks, false, 4*time.Second) {
t.Errorf("wallet notifier didn't terminate after unsubscribe")
}
}
type walletEvent struct {
accounts.WalletEvent
a accounts.Account
}
// Tests that wallet notifications and correctly fired when accounts are added
// or deleted from the keystore.
func TestWalletNotifications(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, false)
// Subscribe to the wallet feed and collect events.
var (
events []walletEvent
updates = make(chan accounts.WalletEvent)
sub = ks.Subscribe(updates)
)
defer sub.Unsubscribe()
go func() {
for {
select {
case ev := <-updates:
events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
case <-sub.Err():
close(updates)
return
}
}
}()
// Randomly add and remove accounts.
var (
live = make(map[common.Address]accounts.Account)
wantEvents []walletEvent
)
for i := 0; i < 1024; i++ {
if create := len(live) == 0 || rand.Int()%4 > 0; create {
// Add a new account and ensure wallet notifications arrives
account, err := ks.NewAccount("")
if err != nil {
t.Fatalf("failed to create test account: %v", err)
}
live[account.Address] = account
wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account})
} else {
// Delete a random account.
var account accounts.Account
for _, a := range live {
account = a
break
}
if err := ks.Delete(account, ""); err != nil {
t.Fatalf("failed to delete test account: %v", err)
}
delete(live, account.Address)
wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account})
}
}
// Shut down the event collector and check events.
sub.Unsubscribe()
for ev := range updates {
events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
}
checkAccounts(t, live, ks.Wallets())
checkEvents(t, wantEvents, events)
}
// TestImportExport tests the import functionality of a keystore.
func TestImportECDSA(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate key: %v", key)
}
if _, err = ks.ImportECDSA(key, "old"); err != nil {
t.Errorf("importing failed: %v", err)
}
if _, err = ks.ImportECDSA(key, "old"); err == nil {
t.Errorf("importing same key twice succeeded")
}
if _, err = ks.ImportECDSA(key, "new"); err == nil {
t.Errorf("importing same key twice succeeded")
}
}
// TestImportECDSA tests the import and export functionality of a keystore.
func TestImportExport(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old")
if err != nil {
t.Fatalf("failed to create account: %v", acc)
}
json, err := ks.Export(acc, "old", "new")
if err != nil {
t.Fatalf("failed to export account: %v", acc)
}
_, ks2 := tmpKeyStore(t, true)
if _, err = ks2.Import(json, "old", "old"); err == nil {
t.Errorf("importing with invalid password succeeded")
}
acc2, err := ks2.Import(json, "new", "new")
if err != nil {
t.Errorf("importing failed: %v", err)
}
if acc.Address != acc2.Address {
t.Error("imported account does not match exported account")
}
if _, err = ks2.Import(json, "new", "new"); err == nil {
t.Errorf("importing a key twice succeeded")
}
}
// TestImportRace tests the keystore on races.
// This test should fail under -race if importing races.
func TestImportRace(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old")
if err != nil {
t.Fatalf("failed to create account: %v", acc)
}
json, err := ks.Export(acc, "old", "new")
if err != nil {
t.Fatalf("failed to export account: %v", acc)
}
_, ks2 := tmpKeyStore(t, true)
var atom atomic.Uint32
var wg sync.WaitGroup
wg.Add(2)
for i := 0; i < 2; i++ {
go func() {
defer wg.Done()
if _, err := ks2.Import(json, "new", "new"); err != nil {
atom.Add(1)
}
}()
}
wg.Wait()
if atom.Load() != 1 {
t.Errorf("Import is racy")
}
}
// checkAccounts checks that all known live accounts are present in the wallet list.
func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) {
if len(live) != len(wallets) {
t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live))
return
}
liveList := make([]accounts.Account, 0, len(live))
for _, account := range live {
liveList = append(liveList, account)
}
slices.SortFunc(liveList, byURL)
for j, wallet := range wallets {
if accs := wallet.Accounts(); len(accs) != 1 {
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
} else if accs[0] != liveList[j] {
t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j])
}
}
}
// checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times.
func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
for _, wantEv := range want {
nmatch := 0
for ; len(have) > 0; nmatch++ {
if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a {
break
}
have = have[1:]
}
if nmatch == 0 {
t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address)
}
}
}
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
d := t.TempDir()
newKs := NewPlaintextKeyStore
if encrypted {
newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
}
return d, newKs(d)
}
-378
View File
@@ -1,378 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
/*
This key store behaves as KeyStorePlain with the difference that
the private key is encrypted and on disk uses another JSON encoding.
The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
*/
package keystore
import (
"bytes"
"crypto/aes"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/google/uuid"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
)
const (
keyHeaderKDF = "scrypt"
// StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB
// memory and taking approximately 1s CPU time on a modern processor.
StandardScryptN = 1 << 18
// StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB
// memory and taking approximately 1s CPU time on a modern processor.
StandardScryptP = 1
// LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB
// memory and taking approximately 100ms CPU time on a modern processor.
LightScryptN = 1 << 12
// LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB
// memory and taking approximately 100ms CPU time on a modern processor.
LightScryptP = 6
scryptR = 8
scryptDKLen = 32
)
type keyStorePassphrase struct {
keysDirPath string
scryptN int
scryptP int
// skipKeyFileVerification disables the security-feature which does
// reads and decrypts any newly created keyfiles. This should be 'false' in all
// cases except tests -- setting this to 'true' is not recommended.
skipKeyFileVerification bool
}
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
// Load the key from the keystore and decrypt its contents
keyjson, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
key, err := DecryptKey(keyjson, auth)
if err != nil {
return nil, err
}
// Make sure we're really operating on the requested key (no swap attacks)
if key.Address != addr {
return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr)
}
return key, nil
}
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) {
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)
return a, err
}
func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
if err != nil {
return err
}
// Write into temporary file
tmpName, err := writeTemporaryKeyFile(filename, keyjson)
if err != nil {
return err
}
if !ks.skipKeyFileVerification {
// Verify that we can decrypt the file with the given password.
_, err = ks.GetKey(key.Address, tmpName, auth)
if err != nil {
msg := "An error was encountered when saving and verifying the keystore file. \n" +
"This indicates that the keystore is corrupted. \n" +
"The corrupted file is stored at \n%v\n" +
"Please file a ticket at:\n\n" +
"https://github.com/ethereum/go-ethereum/issues." +
"The error was : %s"
//lint:ignore ST1005 This is a message for the user
return fmt.Errorf(msg, tmpName, err)
}
}
return os.Rename(tmpName, filename)
}
func (ks keyStorePassphrase) JoinPath(filename string) string {
if filepath.IsAbs(filename) {
return filename
}
return filepath.Join(ks.keysDirPath, filename)
}
// EncryptDataV3 encrypts the data given as 'data' with the password 'auth'.
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
panic("reading from crypto/rand failed: " + err.Error())
}
derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
if err != nil {
return CryptoJSON{}, err
}
encryptKey := derivedKey[:16]
iv := make([]byte, aes.BlockSize) // 16
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("reading from crypto/rand failed: " + err.Error())
}
cipherText, err := aesCTRXOR(encryptKey, data, iv)
if err != nil {
return CryptoJSON{}, err
}
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
scryptParamsJSON := make(map[string]interface{}, 5)
scryptParamsJSON["n"] = scryptN
scryptParamsJSON["r"] = scryptR
scryptParamsJSON["p"] = scryptP
scryptParamsJSON["dklen"] = scryptDKLen
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
cipherParamsJSON := cipherparamsJSON{
IV: hex.EncodeToString(iv),
}
cryptoStruct := CryptoJSON{
Cipher: "aes-128-ctr",
CipherText: hex.EncodeToString(cipherText),
CipherParams: cipherParamsJSON,
KDF: keyHeaderKDF,
KDFParams: scryptParamsJSON,
MAC: hex.EncodeToString(mac),
}
return cryptoStruct, nil
}
// EncryptKey encrypts a key using the specified scrypt parameters into a json
// blob that can be decrypted later on.
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
if err != nil {
return nil, err
}
encryptedKeyJSONV3 := encryptedKeyJSONV3{
hex.EncodeToString(key.Address[:]),
cryptoStruct,
key.Id.String(),
version,
}
return json.Marshal(encryptedKeyJSONV3)
}
// DecryptKey decrypts a key from a json blob, returning the private key itself.
func DecryptKey(keyjson []byte, auth string) (*Key, error) {
// Parse the json into a simple map to fetch the key version
m := make(map[string]interface{})
if err := json.Unmarshal(keyjson, &m); err != nil {
return nil, err
}
// Depending on the version try to parse one way or another
var (
keyBytes, keyId []byte
err error
)
if version, ok := m["version"].(string); ok && version == "1" {
k := new(encryptedKeyJSONV1)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV1(k, auth)
} else {
k := new(encryptedKeyJSONV3)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV3(k, auth)
}
// Handle any decryption errors and return the key
if err != nil {
return nil, err
}
key, err := crypto.ToECDSA(keyBytes)
if err != nil {
return nil, fmt.Errorf("invalid key: %w", err)
}
id, err := uuid.FromBytes(keyId)
if err != nil {
return nil, fmt.Errorf("invalid UUID: %w", err)
}
return &Key{
Id: id,
Address: crypto.PubkeyToAddress(key.PublicKey),
PrivateKey: key,
}, nil
}
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
if cryptoJson.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher)
}
mac, err := hex.DecodeString(cryptoJson.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(cryptoJson, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
if keyProtected.Version != version {
return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version)
}
keyUUID, err := uuid.Parse(keyProtected.Id)
if err != nil {
return nil, nil, err
}
keyId = keyUUID[:]
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
keyUUID, err := uuid.Parse(keyProtected.Id)
if err != nil {
return nil, nil, err
}
keyId = keyUUID[:]
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
if err != nil {
return nil, nil, err
}
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
if err != nil {
return nil, nil, err
}
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
if err != nil {
return nil, nil, err
}
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, nil, ErrDecrypt
}
plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
authArray := []byte(auth)
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
if err != nil {
return nil, err
}
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
if cryptoJSON.KDF == keyHeaderKDF {
n := ensureInt(cryptoJSON.KDFParams["n"])
r := ensureInt(cryptoJSON.KDFParams["r"])
p := ensureInt(cryptoJSON.KDFParams["p"])
return scrypt.Key(authArray, salt, n, r, p, dkLen)
} else if cryptoJSON.KDF == "pbkdf2" {
c := ensureInt(cryptoJSON.KDFParams["c"])
prf := cryptoJSON.KDFParams["prf"].(string)
if prf != "hmac-sha256" {
return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf)
}
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
return key, nil
}
return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF)
}
// TODO: can we do without this when unmarshalling dynamic JSON?
// why do integers in KDF params end up as float64 and not int after
// unmarshal?
func ensureInt(x interface{}) int {
res, ok := x.(int)
if !ok {
res = int(x.(float64))
}
return res
}
-71
View File
@@ -1,71 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
)
const (
veryLightScryptN = 2
veryLightScryptP = 1
)
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
func TestKeyEncryptDecrypt(t *testing.T) {
t.Parallel()
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
if err != nil {
t.Fatal(err)
}
password := ""
address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8")
// Do a few rounds of decryption and encryption
for i := 0; i < 3; i++ {
// Try a bad password first
if _, err := DecryptKey(keyjson, password+"bad"); err == nil {
t.Errorf("test %d: json key decrypted with bad password", i)
}
// Decrypt with the correct password
key, err := DecryptKey(keyjson, password)
if err != nil {
t.Fatalf("test %d: json key failed to decrypt: %v", i, err)
}
if key.Address != address {
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
}
// Recrypt with a new password and start over
password += "new data appended" // nolint: gosec
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
t.Errorf("test %d: failed to re-encrypt key %v", i, err)
}
}
}
-71
View File
@@ -1,71 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
)
type keyStorePlain struct {
keysDirPath string
}
func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) {
fd, err := os.Open(filename)
if err != nil {
return nil, err
}
defer fd.Close()
key := new(Key)
if err := json.NewDecoder(fd).Decode(key); err != nil {
return nil, err
}
if key.Address != addr {
return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr)
}
return key, nil
}
func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
content, err := json.Marshal(key)
if err != nil {
return err
}
return writeKeyFile(filename, content)
}
func (ks keyStorePlain) JoinPath(filename string) string {
if filepath.IsAbs(filename) {
return filename
}
return filepath.Join(ks.keysDirPath, filename)
}
-228
View File
@@ -1,228 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"crypto/rand"
"encoding/hex"
"fmt"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
)
func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
d := t.TempDir()
if encrypted {
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true}
} else {
ks = &keyStorePlain{d}
}
return d, ks
}
func TestKeyStorePlain(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStoreIface(t, false)
pass := "" // not used but required by API
k1, account, err := storeNewKey(ks, rand.Reader, pass)
if err != nil {
t.Fatal(err)
}
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.Address, k2.Address) {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
t.Fatal(err)
}
}
func TestKeyStorePassphrase(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStoreIface(t, true)
pass := "foo"
k1, account, err := storeNewKey(ks, rand.Reader, pass)
if err != nil {
t.Fatal(err)
}
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.Address, k2.Address) {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
t.Fatal(err)
}
}
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStoreIface(t, true)
pass := "foo"
k1, account, err := storeNewKey(ks, rand.Reader, pass)
if err != nil {
t.Fatal(err)
}
if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt)
}
}
func TestImportPreSaleKey(t *testing.T) {
t.Parallel()
dir, ks := tmpKeyStoreIface(t, true)
// file content of a presale key file generated with:
// python pyethsaletool.py genwallet
// with password "foo"
fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"[email protected]\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}"
pass := "foo"
account, _, err := importPreSaleKey(ks, []byte(fileContent), pass)
if err != nil {
t.Fatal(err)
}
if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") {
t.Errorf("imported account has wrong address %x", account.Address)
}
if !strings.HasPrefix(account.URL.Path, dir) {
t.Errorf("imported account file not in keystore directory: %q", account.URL)
}
}
// Test and utils for the key store tests in the Ethereum JSON tests;
// testdataKeyStoreTests/basic_tests.json
type KeyStoreTestV3 struct {
Json encryptedKeyJSONV3
Password string
Priv string
}
type KeyStoreTestV1 struct {
Json encryptedKeyJSONV1
Password string
Priv string
}
func TestV3_PBKDF2_1(t *testing.T) {
t.Parallel()
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
}
var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests")
func skipIfSubmoduleMissing(t *testing.T) {
if !common.FileExist(testsSubmodule) {
t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule)
}
}
func TestV3_PBKDF2_2(t *testing.T) {
skipIfSubmoduleMissing(t)
t.Parallel()
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
testDecryptV3(tests["test1"], t)
}
func TestV3_PBKDF2_3(t *testing.T) {
skipIfSubmoduleMissing(t)
t.Parallel()
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
}
func TestV3_PBKDF2_4(t *testing.T) {
skipIfSubmoduleMissing(t)
t.Parallel()
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
testDecryptV3(tests["evilnonce"], t)
}
func TestV3_Scrypt_1(t *testing.T) {
t.Parallel()
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
testDecryptV3(tests["wikipage_test_vector_scrypt"], t)
}
func TestV3_Scrypt_2(t *testing.T) {
skipIfSubmoduleMissing(t)
t.Parallel()
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
testDecryptV3(tests["test2"], t)
}
func testDecryptV3(test KeyStoreTestV3, t *testing.T) {
privBytes, _, err := decryptKeyV3(&test.Json, test.Password)
if err != nil {
t.Fatal(err)
}
privHex := hex.EncodeToString(privBytes)
if test.Priv != privHex {
t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
}
}
func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 {
tests := make(map[string]KeyStoreTestV3)
err := common.LoadJSON(file, &tests)
if err != nil {
t.Fatal(err)
}
return tests
}
func TestKeyForDirectICAP(t *testing.T) {
t.Parallel()
key := NewKeyForDirectICAP(rand.Reader)
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex())
}
}
func TestV3_31_Byte_Key(t *testing.T) {
t.Parallel()
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
testDecryptV3(tests["31_byte_key"], t)
}
func TestV3_30_Byte_Key(t *testing.T) {
t.Parallel()
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
testDecryptV3(tests["30_byte_key"], t)
}
-160
View File
@@ -1,160 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ethereum/go-ethereum/crypto"
"github.com/google/uuid"
"golang.org/x/crypto/pbkdf2"
)
// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) {
key, err := decryptPreSaleKey(keyJSON, password)
if err != nil {
return accounts.Account{}, nil, err
}
key.Id, err = uuid.NewRandom()
if err != nil {
return accounts.Account{}, nil, err
}
a := accounts.Account{
Address: key.Address,
URL: accounts.URL{
Scheme: KeyStoreScheme,
Path: keyStore.JoinPath(keyFileName(key.Address)),
},
}
err = keyStore.StoreKey(a.URL.Path, key, password)
return a, key, err
}
func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) {
preSaleKeyStruct := struct {
EncSeed string
EthAddr string
Email string
BtcAddr string
}{}
err = json.Unmarshal(fileContent, &preSaleKeyStruct)
if err != nil {
return nil, err
}
encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
if err != nil {
return nil, errors.New("invalid hex in encSeed")
}
if len(encSeedBytes) < 16 {
return nil, errors.New("invalid encSeed, too short")
}
iv := encSeedBytes[:16]
cipherText := encSeedBytes[16:]
/*
See https://github.com/ethereum/pyethsaletool
pyethsaletool generates the encryption key from password by
2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:().
16 byte key length within PBKDF2 and resulting key is used as AES key
*/
passBytes := []byte(password)
derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
if err != nil {
return nil, err
}
ethPriv := crypto.Keccak256(plainText)
ecKey := crypto.ToECDSAUnsafe(ethPriv)
key = &Key{
Id: uuid.UUID{},
Address: crypto.PubkeyToAddress(ecKey.PublicKey),
PrivateKey: ecKey,
}
derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
expectedAddr := preSaleKeyStruct.EthAddr
if derivedAddr != expectedAddr {
err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr)
}
return key, err
}
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
// AES-128 is selected due to size of encryptKey.
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(aesBlock, iv)
outText := make([]byte, len(inText))
stream.XORKeyStream(outText, inText)
return outText, err
}
func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
paddedPlaintext := make([]byte, len(cipherText))
decrypter.CryptBlocks(paddedPlaintext, cipherText)
plaintext := pkcs7Unpad(paddedPlaintext)
if plaintext == nil {
return nil, ErrDecrypt
}
return plaintext, err
}
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
func pkcs7Unpad(in []byte) []byte {
if len(in) == 0 {
return nil
}
padding := in[len(in)-1]
if int(padding) > len(in) || padding > aes.BlockSize {
return nil
} else if padding == 0 {
return nil
}
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
if in[i] != padding {
return nil
}
}
return in[:len(in)-int(padding)]
}
-1
View File
@@ -1 +0,0 @@
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
-1
View File
@@ -1 +0,0 @@
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
-1
View File
@@ -1 +0,0 @@
{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
-1
View File
@@ -1 +0,0 @@
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
-21
View File
@@ -1,21 +0,0 @@
This directory contains accounts for testing.
The password that unlocks them is "foobar".
The "good" key files which are supposed to be loadable are:
- File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8
- File: aaa
Address: 0xf466859ead1932d743d622cb74fc058882e8648a
- File: zzz
Address: 0x289d485d9771714cce91d3393d764e1311907acc
The other files (including this README) are broken in various ways
and should not be picked up by package accounts:
- File: no-address (missing address field, otherwise same as "aaa")
- File: garbage (file with random data)
- File: empty (file with no content)
- File: swapfile~ (should be skipped)
- File: .hiddenfile (should be skipped)
- File: foo/... (should be skipped because it is a directory)
@@ -1 +0,0 @@
{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
-1
View File
@@ -1 +0,0 @@
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
View File
@@ -1 +0,0 @@
{"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3}
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
{"crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
-1
View File
@@ -1 +0,0 @@
{"address":"0000000000000000000000000000000000000000","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
-1
View File
@@ -1 +0,0 @@
{"address":"289d485d9771714cce91d3393d764e1311907acc","crypto":{"cipher":"aes-128-ctr","ciphertext":"faf32ca89d286b107f5e6d842802e05263c49b78d46eac74e6109e9a963378ab","cipherparams":{"iv":"558833eec4a665a8c55608d7d503407d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"d571fff447ffb24314f9513f5160246f09997b857ac71348b73e785aab40dc04"},"mac":"21edb85ff7d0dab1767b9bf498f2c3cb7be7609490756bd32300bb213b59effe"},"id":"3279afcf-55ba-43ff-8997-02dcc46a6525","version":3}
@@ -1 +0,0 @@
{"address":"cb61d5a9c4896fb9658090b597ef0e7be6f7b67e","Crypto":{"cipher":"aes-128-cbc","ciphertext":"6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0","cipherparams":{"iv":"35337770fc2117994ecdcad026bccff4"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"},"mac":"3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644","version":"1"},"id":"e25f7c1f-d318-4f29-b62c-687190d4d299","version":"1"}
-28
View File
@@ -1,28 +0,0 @@
{
"test1": {
"json": {
"Crypto": {
"cipher": "aes-128-cbc",
"cipherparams": {
"iv": "35337770fc2117994ecdcad026bccff4"
},
"ciphertext": "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"
},
"mac": "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644",
"version": "1"
},
"address": "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e",
"id": "e25f7c1f-d318-4f29-b62c-687190d4d299",
"version": "1"
},
"password": "g",
"priv": "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
}
}
-97
View File
@@ -1,97 +0,0 @@
{
"wikipage_test_vector_scrypt": {
"json": {
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "83dbcc02d8ccb40e466191a123791e0e"
},
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 262144,
"r" : 1,
"p" : 8,
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
},
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
},
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version" : 3
},
"password": "testpassword",
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
},
"wikipage_test_vector_pbkdf2": {
"json": {
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
},
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
"kdf" : "pbkdf2",
"kdfparams" : {
"c" : 262144,
"dklen" : 32,
"prf" : "hmac-sha256",
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
},
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
},
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version" : 3
},
"password": "testpassword",
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
},
"31_byte_key": {
"json": {
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "e0c41130a323adc1446fc82f724bca2f"
},
"ciphertext" : "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 2,
"r" : 8,
"p" : 1,
"salt" : "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63"
},
"mac" : "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5"
},
"id" : "fecfc4ce-e956-48fd-953b-30f8b52ed66c",
"version" : 3
},
"password": "foo",
"priv": "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35"
},
"30_byte_key": {
"json": {
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "3ca92af36ad7c2cd92454c59cea5ef00"
},
"ciphertext" : "108b7d34f3442fc26ab1ab90ca91476ba6bfa8c00975a49ef9051dc675aa",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 2,
"r" : 8,
"p" : 1,
"salt" : "d0769e608fb86cda848065642a9c6fa046845c928175662b8e356c77f914cd3b"
},
"mac" : "75d0e6759f7b3cefa319c3be41680ab6beea7d8328653474bd06706d4cc67420"
},
"id" : "a37e1559-5955-450d-8075-7b8931b392b2",
"version" : 3
},
"password": "foo",
"priv": "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
}
}
-1
View File
@@ -1 +0,0 @@
{"address":"45dea0fb0bba44f4fcf290bba71fd57d7117cbb8","crypto":{"cipher":"aes-128-ctr","ciphertext":"b87781948a1befd247bff51ef4063f716cf6c2d3481163e9a8f42e1f9bb74145","cipherparams":{"iv":"dc4926b48a105133d2f16b96833abf1e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":2,"p":1,"r":8,"salt":"004244bbdc51cadda545b1cfa43cff9ed2ae88e08c61f1479dbb45410722f8f0"},"mac":"39990c1684557447940d4c69e06b1b82b2aceacb43f284df65c956daf3046b85"},"id":"ce541d8d-c79b-40f8-9f8c-20f59616faba","version":3}
-160
View File
@@ -1,160 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"math/big"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/interfaces"
"github.com/ethereum/go-ethereum/crypto"
)
// keystoreWallet implements the accounts.Wallet interface for the original
// keystore.
type keystoreWallet struct {
account accounts.Account // Single account contained in this wallet
keystore *KeyStore // Keystore where the account originates from
}
// URL implements accounts.Wallet, returning the URL of the account within.
func (w *keystoreWallet) URL() accounts.URL {
return w.account.URL
}
// Status implements accounts.Wallet, returning whether the account held by the
// keystore wallet is unlocked or not.
func (w *keystoreWallet) Status() (string, error) {
w.keystore.mu.RLock()
defer w.keystore.mu.RUnlock()
if _, ok := w.keystore.unlocked[w.account.Address]; ok {
return "Unlocked", nil
}
return "Locked", nil
}
// Open implements accounts.Wallet, but is a noop for plain wallets since there
// is no connection or decryption step necessary to access the list of accounts.
func (w *keystoreWallet) Open(passphrase string) error { return nil }
// Close implements accounts.Wallet, but is a noop for plain wallets since there
// is no meaningful open operation.
func (w *keystoreWallet) Close() error { return nil }
// Accounts implements accounts.Wallet, returning an account list consisting of
// a single account that the plain keystore wallet contains.
func (w *keystoreWallet) Accounts() []accounts.Account {
return []accounts.Account{w.account}
}
// Contains implements accounts.Wallet, returning whether a particular account is
// or is not wrapped by this wallet instance.
func (w *keystoreWallet) Contains(account accounts.Account) bool {
return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL)
}
// Derive implements accounts.Wallet, but is a noop for plain wallets since there
// is no notion of hierarchical account derivation for plain keystore accounts.
func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
return accounts.Account{}, accounts.ErrNotSupported
}
// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since
// there is no notion of hierarchical account derivation for plain keystore accounts.
func (w *keystoreWallet) SelfDerive(bases []accounts.DerivationPath, chain interfaces.ChainStateReader) {
}
// signHash attempts to sign the given hash with
// the given account. If the wallet does not wrap this particular account, an
// error is returned to avoid account leakage (even though in theory we may be
// able to sign via our shared keystore backend).
func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
// Make sure the requested account is contained within
if !w.Contains(account) {
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.SignHash(account, hash)
}
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed.
func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
return w.signHash(account, crypto.Keccak256(data))
}
// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed.
func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
// Make sure the requested account is contained within
if !w.Contains(account) {
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data))
}
// SignText implements accounts.Wallet, attempting to sign the hash of
// the given text with the given account.
func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
return w.signHash(account, accounts.TextHash(text))
}
// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the
// hash of the given text with the given account using passphrase as extra authentication.
func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
// Make sure the requested account is contained within
if !w.Contains(account) {
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
}
// SignTx implements accounts.Wallet, attempting to sign the given transaction
// with the given account. If the wallet does not wrap this particular account,
// an error is returned to avoid account leakage (even though in theory we may
// be able to sign via our shared keystore backend).
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// Make sure the requested account is contained within
if !w.Contains(account) {
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.SignTx(account, tx, chainID)
}
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
// transaction with the given account using passphrase as extra authentication.
func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// Make sure the requested account is contained within
if !w.Contains(account) {
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)
}
-144
View File
@@ -1,144 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
package keystore
import (
"os"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/fsnotify/fsnotify"
)
type watcher struct {
ac *accountCache
running bool // set to true when runloop begins
runEnded bool // set to true when runloop ends
starting bool // set to true prior to runloop starting
quit chan struct{}
}
func newWatcher(ac *accountCache) *watcher {
return &watcher{
ac: ac,
quit: make(chan struct{}),
}
}
// enabled returns false on systems not supported.
func (*watcher) enabled() bool { return true }
// starts the watcher loop in the background.
// Start a watcher in the background if that's not already in progress.
// The caller must hold w.ac.mu.
func (w *watcher) start() {
if w.starting || w.running {
return
}
w.starting = true
go w.loop()
}
func (w *watcher) close() {
close(w.quit)
}
func (w *watcher) loop() {
defer func() {
w.ac.mu.Lock()
w.running = false
w.starting = false
w.runEnded = true
w.ac.mu.Unlock()
}()
logger := log.New("path", w.ac.keydir)
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Error("Failed to start filesystem watcher", "err", err)
return
}
defer watcher.Close()
if err := watcher.Add(w.ac.keydir); err != nil {
if !os.IsNotExist(err) {
logger.Warn("Failed to watch keystore folder", "err", err)
}
return
}
logger.Trace("Started watching keystore folder", "folder", w.ac.keydir)
defer logger.Trace("Stopped watching keystore folder")
w.ac.mu.Lock()
w.running = true
w.ac.mu.Unlock()
// Wait for file system events and reload.
// When an event occurs, the reload call is delayed a bit so that
// multiple events arriving quickly only cause a single reload.
var (
debounceDuration = 500 * time.Millisecond
rescanTriggered = false
debounce = time.NewTimer(0)
)
// Ignore initial trigger
if !debounce.Stop() {
<-debounce.C
}
defer debounce.Stop()
for {
select {
case <-w.quit:
return
case _, ok := <-watcher.Events:
if !ok {
return
}
// Trigger the scan (with delay), if not already triggered
if !rescanTriggered {
debounce.Reset(debounceDuration)
rescanTriggered = true
}
// The fsnotify library does provide more granular event-info, it
// would be possible to refresh individual affected files instead
// of scheduling a full rescan. For most cases though, the
// full rescan is quick and obviously simplest.
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Info("Filesystem watcher error", "err", err)
case <-debounce.C:
w.ac.scanAccounts()
rescanTriggered = false
}
}
}
-45
View File
@@ -1,45 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
// This is the fallback implementation of directory watching.
// It is used on unsupported platforms.
package keystore
type watcher struct {
running bool
runEnded bool
}
func newWatcher(*accountCache) *watcher { return new(watcher) }
func (*watcher) start() {}
func (*watcher) close() {}
// enabled returns false on systems not supported.
func (*watcher) enabled() bool { return false }
-285
View File
@@ -1,285 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
import (
"reflect"
"sort"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
)
// managerSubBufferSize determines how many incoming wallet events
// the manager will buffer in its channel.
const managerSubBufferSize = 50
// Config contains the settings of the global account manager.
//
// TODO(rjl493456442, karalabe, holiman): Get rid of this when account management
// is removed in favor of Clef.
type Config struct {
InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed
}
// newBackendEvent lets the manager know it should
// track the given backend for wallet updates.
type newBackendEvent struct {
backend Backend
processed chan struct{} // Informs event emitter that backend has been integrated
}
// Manager is an overarching account manager that can communicate with various
// backends for signing transactions.
type Manager struct {
config *Config // Global account manager configurations
backends map[reflect.Type][]Backend // Index of backends currently registered
updaters []event.Subscription // Wallet update subscriptions for all backends
updates chan WalletEvent // Subscription sink for backend wallet changes
newBackends chan newBackendEvent // Incoming backends to be tracked by the manager
wallets []Wallet // Cache of all wallets from all registered backends
feed event.Feed // Wallet feed notifying of arrivals/departures
quit chan chan error
term chan struct{} // Channel is closed upon termination of the update loop
lock sync.RWMutex
}
// NewManager creates a generic account manager to sign transaction via various
// supported backends.
func NewManager(config *Config, backends ...Backend) *Manager {
// Retrieve the initial list of wallets from the backends and sort by URL
var wallets []Wallet
for _, backend := range backends {
wallets = merge(wallets, backend.Wallets()...)
}
// Subscribe to wallet notifications from all backends
updates := make(chan WalletEvent, managerSubBufferSize)
subs := make([]event.Subscription, len(backends))
for i, backend := range backends {
subs[i] = backend.Subscribe(updates)
}
// Assemble the account manager and return
am := &Manager{
config: config,
backends: make(map[reflect.Type][]Backend),
updaters: subs,
updates: updates,
newBackends: make(chan newBackendEvent),
wallets: wallets,
quit: make(chan chan error),
term: make(chan struct{}),
}
for _, backend := range backends {
kind := reflect.TypeOf(backend)
am.backends[kind] = append(am.backends[kind], backend)
}
go am.update()
return am
}
// Close terminates the account manager's internal notification processes.
func (am *Manager) Close() error {
for _, w := range am.wallets {
w.Close()
}
errc := make(chan error)
am.quit <- errc
return <-errc
}
// Config returns the configuration of account manager.
func (am *Manager) Config() *Config {
return am.config
}
// AddBackend starts the tracking of an additional backend for wallet updates.
// cmd/geth assumes once this func returns the backends have been already integrated.
func (am *Manager) AddBackend(backend Backend) {
done := make(chan struct{})
am.newBackends <- newBackendEvent{backend, done}
<-done
}
// update is the wallet event loop listening for notifications from the backends
// and updating the cache of wallets.
func (am *Manager) update() {
// Close all subscriptions when the manager terminates
defer func() {
am.lock.Lock()
for _, sub := range am.updaters {
sub.Unsubscribe()
}
am.updaters = nil
am.lock.Unlock()
}()
// Loop until termination
for {
select {
case event := <-am.updates:
// Wallet event arrived, update local cache
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()
// Notify any listeners of the event
am.feed.Send(event)
case event := <-am.newBackends:
am.lock.Lock()
// Update caches
backend := event.backend
am.wallets = merge(am.wallets, backend.Wallets()...)
am.updaters = append(am.updaters, backend.Subscribe(am.updates))
kind := reflect.TypeOf(backend)
am.backends[kind] = append(am.backends[kind], backend)
am.lock.Unlock()
close(event.processed)
case errc := <-am.quit:
// Manager terminating, return
errc <- nil
// Signals event emitters the loop is not receiving values
// to prevent them from getting stuck.
close(am.term)
return
}
}
}
// Backends retrieves the backend(s) with the given type from the account manager.
func (am *Manager) Backends(kind reflect.Type) []Backend {
am.lock.RLock()
defer am.lock.RUnlock()
return am.backends[kind]
}
// Wallets returns all signer accounts registered under this account manager.
func (am *Manager) Wallets() []Wallet {
am.lock.RLock()
defer am.lock.RUnlock()
return am.walletsNoLock()
}
// walletsNoLock returns all registered wallets. Callers must hold am.lock.
func (am *Manager) walletsNoLock() []Wallet {
cpy := make([]Wallet, len(am.wallets))
copy(cpy, am.wallets)
return cpy
}
// Wallet retrieves the wallet associated with a particular 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.walletsNoLock() {
if wallet.URL() == parsed {
return wallet, nil
}
}
return nil, ErrUnknownWallet
}
// Accounts returns all account addresses of all wallets within the account manager
func (am *Manager) Accounts() []common.Address {
am.lock.RLock()
defer am.lock.RUnlock()
addresses := make([]common.Address, 0) // return [] instead of nil if empty
for _, wallet := range am.wallets {
for _, account := range wallet.Accounts() {
addresses = append(addresses, account.Address)
}
}
return addresses
}
// Find attempts to locate the wallet corresponding to a specific account. Since
// accounts can be dynamically added to and removed from wallets, this method has
// a linear runtime in the number of wallets.
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
}
// Subscribe creates an async subscription to receive notifications when the
// manager detects the arrival or departure of a wallet from any of its backends.
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription {
return am.feed.Subscribe(sink)
}
// merge is a sorted analogue of append for wallets, where the ordering of the
// origin list is preserved by inserting new wallets at the correct position.
//
// The original slice is assumed to be already sorted by URL.
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
}
// drop is the counterpart of merge, which looks up wallets from within the sorted
// cache and removes the ones specified.
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) {
// Wallet not found, may happen during startup
continue
}
slice = append(slice[:n], slice[n+1:]...)
}
return slice
}
-106
View File
@@ -1,106 +0,0 @@
# Using the smartcard wallet
## Requirements
* A USB smartcard reader
* A keycard that supports the status app
* PCSCD version 4.3 running on your system **Only version 4.3 is currently supported**
## Preparing the smartcard
**WARNING: FOLLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS**
You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap)
You also need to make sure that the PCSC daemon is running on your system.
Then, you can install the application to the card by typing:
```
keycard install -a keycard_v2.2.1.cap && keycard init
```
At the end of this process, you will be provided with a PIN, a PUK and a pairing password. Write them down, you'll need them shortly.
Start `geth` with the `console` command. You will notice the following warning:
```
WARN [04-09|16:58:38.898] Failed to open wallet url=keycard://044def09 err="smartcard: pairing password needed"
```
Write down the URL (`keycard://044def09` in this example). Then ask `geth` to open the wallet:
```
> personal.openWallet("keycard://044def09", "pairing password")
```
The pairing password has been generated during the card initialization process.
The process needs to be repeated once more with the PIN:
```
> personal.openWallet("keycard://044def09", "PIN number")
```
If everything goes well, you should see your new account when typing `personal` on the console:
```
> personal
WARN [04-09|17:02:07.330] Smartcard wallet account derivation failed url=keycard://044def09 err="Unexpected response status Cla=0x80, Ins=0xd1, Sw=0x6985"
{
listAccounts: [],
listWallets: [{
status: "Empty, waiting for initialization",
url: "keycard://044def09"
}],
...
}
```
So the communication with the card is working, but there is no key associated with this wallet. Let's create it:
```
> personal.initializeWallet("keycard://044def09")
"tilt ... impact"
```
You should get a list of words, this is your seed so write them down. Your wallet should now be initialized:
```
> personal.listWallets
[{
accounts: [{
address: "0x678b7cd55c61917defb23546a41803c5bfefbc7a",
url: "keycard://044d/m/44'/60'/0'/0/0"
}],
status: "Online",
url: "keycard://044def09"
}]
```
You're all set!
## Usage
1. Start `geth` with the `console` command
2. Check the card's URL by checking `personal.listWallets`:
```
listWallets: [{
status: "Online, can derive public keys",
url: "keycard://a4d73015"
}]
```
3. Open the wallet, you will be prompted for your pairing password, then PIN:
```
personal.openWallet("keycard://a4d73015")
```
4. Check that creation was successful by typing e.g. `personal`. Then use it like a regular wallet.
## Known issues
* Starting geth with a valid card seems to make firefox crash.
* PCSC version 4.4 should work, but is currently untested
-97
View File
@@ -1,97 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package scwallet
import (
"bytes"
"encoding/binary"
"fmt"
)
// commandAPDU represents an application data unit sent to a smartcard.
type commandAPDU struct {
Cla, Ins, P1, P2 uint8 // Class, Instruction, Parameter 1, Parameter 2
Data []byte // Command data
Le uint8 // Command data length
}
// serialize serializes a command APDU.
func (ca commandAPDU) serialize() ([]byte, error) {
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil {
return nil, err
}
if err := binary.Write(buf, binary.BigEndian, ca.Ins); err != nil {
return nil, err
}
if err := binary.Write(buf, binary.BigEndian, ca.P1); err != nil {
return nil, err
}
if err := binary.Write(buf, binary.BigEndian, ca.P2); err != nil {
return nil, err
}
if len(ca.Data) > 0 {
if err := binary.Write(buf, binary.BigEndian, uint8(len(ca.Data))); err != nil {
return nil, err
}
if err := binary.Write(buf, binary.BigEndian, ca.Data); err != nil {
return nil, err
}
}
if err := binary.Write(buf, binary.BigEndian, ca.Le); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// responseAPDU represents an application data unit received from a smart card.
type responseAPDU struct {
Data []byte // response data
Sw1, Sw2 uint8 // status words 1 and 2
}
// deserialize deserializes a response APDU.
func (ra *responseAPDU) deserialize(data []byte) error {
if len(data) < 2 {
return fmt.Errorf("can not deserialize data: payload too short (%d < 2)", len(data))
}
ra.Data = make([]byte, len(data)-2)
buf := bytes.NewReader(data)
if err := binary.Read(buf, binary.BigEndian, &ra.Data); err != nil {
return err
}
if err := binary.Read(buf, binary.BigEndian, &ra.Sw1); err != nil {
return err
}
if err := binary.Read(buf, binary.BigEndian, &ra.Sw2); err != nil {
return err
}
return nil
}
-312
View File
@@ -1,312 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// This package implements support for smartcard-based hardware wallets such as
// the one written by Status: https://github.com/status-im/hardware-wallet
//
// This implementation of smartcard wallets have a different interaction process
// to other types of hardware wallet. The process works like this:
//
// 1. (First use with a given client) Establish a pairing between hardware
// wallet and client. This requires a secret value called a 'pairing password'.
// You can pair with an unpaired wallet with `personal.openWallet(URI, pairing password)`.
// 2. (First use only) Initialize the wallet, which generates a keypair, stores
// it on the wallet, and returns it so the user can back it up. You can
// initialize a wallet with `personal.initializeWallet(URI)`.
// 3. Connect to the wallet using the pairing information established in step 1.
// You can connect to a paired wallet with `personal.openWallet(URI, PIN)`.
// 4. Interact with the wallet as normal.
package scwallet
import (
"encoding/json"
"io"
"os"
"path/filepath"
"sort"
"sync"
"time"
"github.com/ava-labs/subnet-evm/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
pcsc "github.com/gballet/go-libpcsclite"
)
// Scheme is the URI prefix for smartcard wallets.
const Scheme = "keycard"
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
// notifications don't work).
const refreshCycle = time.Second
// refreshThrottling is the minimum time between wallet refreshes to avoid thrashing.
const refreshThrottling = 500 * time.Millisecond
// smartcardPairing contains information about a smart card we have paired with
// or might pair with the hub.
type smartcardPairing struct {
PublicKey []byte `json:"publicKey"`
PairingIndex uint8 `json:"pairingIndex"`
PairingKey []byte `json:"pairingKey"`
Accounts map[common.Address]accounts.DerivationPath `json:"accounts"`
}
// Hub is a accounts.Backend that can find and handle generic PC/SC hardware wallets.
type Hub struct {
scheme string // Protocol scheme prefixing account and wallet URLs.
context *pcsc.Client
datadir string
pairings map[string]smartcardPairing
refreshed time.Time // Time instance when the list of wallets was last refreshed
wallets map[string]*Wallet // Mapping from reader names to wallet instances
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
quit chan chan error
stateLock sync.RWMutex // Protects the internals of the hub from racey access
}
func (hub *Hub) readPairings() error {
hub.pairings = make(map[string]smartcardPairing)
pairingFile, err := os.Open(filepath.Join(hub.datadir, "smartcards.json"))
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
pairingData, err := io.ReadAll(pairingFile)
if err != nil {
return err
}
var pairings []smartcardPairing
if err := json.Unmarshal(pairingData, &pairings); err != nil {
return err
}
for _, pairing := range pairings {
hub.pairings[string(pairing.PublicKey)] = pairing
}
return nil
}
func (hub *Hub) writePairings() error {
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return err
}
defer pairingFile.Close()
pairings := make([]smartcardPairing, 0, len(hub.pairings))
for _, pairing := range hub.pairings {
pairings = append(pairings, pairing)
}
pairingData, err := json.Marshal(pairings)
if err != nil {
return err
}
if _, err := pairingFile.Write(pairingData); err != nil {
return err
}
return nil
}
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
if pairing, ok := hub.pairings[string(wallet.PublicKey)]; ok {
return &pairing
}
return nil
}
func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error {
if pairing == nil {
delete(hub.pairings, string(wallet.PublicKey))
} else {
hub.pairings[string(wallet.PublicKey)] = *pairing
}
return hub.writePairings()
}
// NewHub creates a new hardware wallet manager for smartcards.
func NewHub(daemonPath string, scheme string, datadir string) (*Hub, error) {
context, err := pcsc.EstablishContext(daemonPath, pcsc.ScopeSystem)
if err != nil {
return nil, err
}
hub := &Hub{
scheme: scheme,
context: context,
datadir: datadir,
wallets: make(map[string]*Wallet),
quit: make(chan chan error),
}
if err := hub.readPairings(); err != nil {
return nil, err
}
hub.refreshWallets()
return hub, nil
}
// Wallets implements accounts.Backend, returning all the currently tracked smart
// cards that appear to be hardware wallets.
func (hub *Hub) Wallets() []accounts.Wallet {
// Make sure the list of wallets is up to date
hub.refreshWallets()
hub.stateLock.RLock()
defer hub.stateLock.RUnlock()
cpy := make([]accounts.Wallet, 0, len(hub.wallets))
for _, wallet := range hub.wallets {
cpy = append(cpy, wallet)
}
sort.Sort(accounts.WalletsByURL(cpy))
return cpy
}
// refreshWallets scans the devices attached to the machine and updates the
// list of wallets based on the found devices.
func (hub *Hub) refreshWallets() {
// Don't scan the USB like crazy it the user fetches wallets in a loop
hub.stateLock.RLock()
elapsed := time.Since(hub.refreshed)
hub.stateLock.RUnlock()
if elapsed < refreshThrottling {
return
}
// Retrieve all the smart card reader to check for cards
readers, err := hub.context.ListReaders()
if err != nil {
// This is a perverted hack, the scard library returns an error if no card
// readers are present instead of simply returning an empty list. We don't
// want to fill the user's log with errors, so filter those out.
if err.Error() != "scard: Cannot find a smart card reader." {
log.Error("Failed to enumerate smart card readers", "err", err)
return
}
}
// Transform the current list of wallets into the new one
hub.stateLock.Lock()
events := []accounts.WalletEvent{}
seen := make(map[string]struct{})
for _, reader := range readers {
// Mark the reader as present
seen[reader] = struct{}{}
// If we already know about this card, skip to the next reader, otherwise clean up
if wallet, ok := hub.wallets[reader]; ok {
if err := wallet.ping(); err == nil {
continue
}
wallet.Close()
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
delete(hub.wallets, reader)
}
// New card detected, try to connect to it
card, err := hub.context.Connect(reader, pcsc.ShareShared, pcsc.ProtocolAny)
if err != nil {
log.Debug("Failed to open smart card", "reader", reader, "err", err)
continue
}
wallet := NewWallet(hub, card)
if err = wallet.connect(); err != nil {
log.Debug("Failed to connect to smart card", "reader", reader, "err", err)
card.Disconnect(pcsc.LeaveCard)
continue
}
// Card connected, start tracking among the wallets
hub.wallets[reader] = wallet
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
}
// Remove any wallets no longer present
for reader, wallet := range hub.wallets {
if _, ok := seen[reader]; !ok {
wallet.Close()
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
delete(hub.wallets, reader)
}
}
hub.refreshed = time.Now()
hub.stateLock.Unlock()
for _, event := range events {
hub.updateFeed.Send(event)
}
}
// Subscribe implements accounts.Backend, creating an async subscription to
// receive notifications on the addition or removal of smart card wallets.
func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
// We need the mutex to reliably start/stop the update loop
hub.stateLock.Lock()
defer hub.stateLock.Unlock()
// Subscribe the caller and track the subscriber count
sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink))
// Subscribers require an active notification loop, start it
if !hub.updating {
hub.updating = true
go hub.updater()
}
return sub
}
// updater is responsible for maintaining an up-to-date list of wallets managed
// by the smart card hub, and for firing wallet addition/removal events.
func (hub *Hub) updater() {
for {
// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout
// <-hub.changes
time.Sleep(refreshCycle)
// Run the wallet refresher
hub.refreshWallets()
// If all our subscribers left, stop the updater
hub.stateLock.Lock()
if hub.updateScope.Count() == 0 {
hub.updating = false
hub.stateLock.Unlock()
return
}
hub.stateLock.Unlock()
}
}
-350
View File
@@ -1,350 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package scwallet
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/crypto"
pcsc "github.com/gballet/go-libpcsclite"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/text/unicode/norm"
)
const (
maxPayloadSize = 223
pairP1FirstStep = 0
pairP1LastStep = 1
scSecretLength = 32
scBlockSize = 16
insOpenSecureChannel = 0x10
insMutuallyAuthenticate = 0x11
insPair = 0x12
insUnpair = 0x13
pairingSalt = "Keycard Pairing Password Salt"
)
// SecureChannelSession enables secure communication with a hardware wallet.
type SecureChannelSession struct {
card *pcsc.Card // A handle to the smartcard for communication
secret []byte // A shared secret generated from our ECDSA keys
publicKey []byte // Our own ephemeral public key
PairingKey []byte // A permanent shared secret for a pairing, if present
sessionEncKey []byte // The current session encryption key
sessionMacKey []byte // The current session MAC key
iv []byte // The current IV
PairingIndex uint8 // The pairing index
}
// NewSecureChannelSession creates a new secure channel for the given card and public key.
func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) {
// Generate an ECDSA keypair for ourselves
key, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
cardPublic, err := crypto.UnmarshalPubkey(keyData)
if err != nil {
return nil, fmt.Errorf("could not unmarshal public key from card: %v", err)
}
secret, _ := key.Curve.ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes())
return &SecureChannelSession{
card: card,
secret: secret.Bytes(),
publicKey: elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y),
}, nil
}
// Pair establishes a new pairing with the smartcard.
func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
secretHash := pbkdf2.Key(norm.NFKD.Bytes(pairingPassword), norm.NFKD.Bytes([]byte(pairingSalt)), 50000, 32, sha256.New)
challenge := make([]byte, 32)
if _, err := rand.Read(challenge); err != nil {
return err
}
response, err := s.pair(pairP1FirstStep, challenge)
if err != nil {
return err
}
md := sha256.New()
md.Write(secretHash[:])
md.Write(challenge)
expectedCryptogram := md.Sum(nil)
cardCryptogram := response.Data[:32]
cardChallenge := response.Data[32:64]
if !bytes.Equal(expectedCryptogram, cardCryptogram) {
return fmt.Errorf("invalid card cryptogram %v != %v", expectedCryptogram, cardCryptogram)
}
md.Reset()
md.Write(secretHash[:])
md.Write(cardChallenge)
response, err = s.pair(pairP1LastStep, md.Sum(nil))
if err != nil {
return err
}
md.Reset()
md.Write(secretHash[:])
md.Write(response.Data[1:])
s.PairingKey = md.Sum(nil)
s.PairingIndex = response.Data[0]
return nil
}
// Unpair disestablishes an existing pairing.
func (s *SecureChannelSession) Unpair() error {
if s.PairingKey == nil {
return errors.New("cannot unpair: not paired")
}
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
if err != nil {
return err
}
s.PairingKey = nil
// Close channel
s.iv = nil
return nil
}
// Open initializes the secure channel.
func (s *SecureChannelSession) Open() error {
if s.iv != nil {
return errors.New("session already opened")
}
response, err := s.open()
if err != nil {
return err
}
// Generate the encryption/mac key by hashing our shared secret,
// pairing key, and the first bytes returned from the Open APDU.
md := sha512.New()
md.Write(s.secret)
md.Write(s.PairingKey)
md.Write(response.Data[:scSecretLength])
keyData := md.Sum(nil)
s.sessionEncKey = keyData[:scSecretLength]
s.sessionMacKey = keyData[scSecretLength : scSecretLength*2]
// The IV is the last bytes returned from the Open APDU.
s.iv = response.Data[scSecretLength:]
return s.mutuallyAuthenticate()
}
// mutuallyAuthenticate is an internal method to authenticate both ends of the
// connection.
func (s *SecureChannelSession) mutuallyAuthenticate() error {
data := make([]byte, scSecretLength)
if _, err := rand.Read(data); err != nil {
return err
}
response, err := s.transmitEncrypted(claSCWallet, insMutuallyAuthenticate, 0, 0, data)
if err != nil {
return err
}
if response.Sw1 != 0x90 || response.Sw2 != 0x00 {
return fmt.Errorf("got unexpected response from MUTUALLY_AUTHENTICATE: %#x%x", response.Sw1, response.Sw2)
}
if len(response.Data) != scSecretLength {
return fmt.Errorf("response from MUTUALLY_AUTHENTICATE was %d bytes, expected %d", len(response.Data), scSecretLength)
}
return nil
}
// open is an internal method that sends an open APDU.
func (s *SecureChannelSession) open() (*responseAPDU, error) {
return transmit(s.card, &commandAPDU{
Cla: claSCWallet,
Ins: insOpenSecureChannel,
P1: s.PairingIndex,
P2: 0,
Data: s.publicKey,
Le: 0,
})
}
// pair is an internal method that sends a pair APDU.
func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error) {
return transmit(s.card, &commandAPDU{
Cla: claSCWallet,
Ins: insPair,
P1: p1,
P2: 0,
Data: data,
Le: 0,
})
}
// transmitEncrypted sends an encrypted message, and decrypts and returns the response.
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
if s.iv == nil {
return nil, errors.New("channel not open")
}
data, err := s.encryptAPDU(data)
if err != nil {
return nil, err
}
meta := [16]byte{cla, ins, p1, p2, byte(len(data) + scBlockSize)}
if err = s.updateIV(meta[:], data); err != nil {
return nil, err
}
fulldata := make([]byte, len(s.iv)+len(data))
copy(fulldata, s.iv)
copy(fulldata[len(s.iv):], data)
response, err := transmit(s.card, &commandAPDU{
Cla: cla,
Ins: ins,
P1: p1,
P2: p2,
Data: fulldata,
})
if err != nil {
return nil, err
}
rmeta := [16]byte{byte(len(response.Data))}
rmac := response.Data[:len(s.iv)]
rdata := response.Data[len(s.iv):]
plainData, err := s.decryptAPDU(rdata)
if err != nil {
return nil, err
}
if err = s.updateIV(rmeta[:], rdata); err != nil {
return nil, err
}
if !bytes.Equal(s.iv, rmac) {
return nil, errors.New("invalid MAC in response")
}
rapdu := &responseAPDU{}
rapdu.deserialize(plainData)
if rapdu.Sw1 != sw1Ok {
return nil, fmt.Errorf("unexpected response status Cla=%#x, Ins=%#x, Sw=%#x%x", cla, ins, rapdu.Sw1, rapdu.Sw2)
}
return rapdu, nil
}
// encryptAPDU is an internal method that serializes and encrypts an APDU.
func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) {
if len(data) > maxPayloadSize {
return nil, fmt.Errorf("payload of %d bytes exceeds maximum of %d", len(data), maxPayloadSize)
}
data = pad(data, 0x80)
ret := make([]byte, len(data))
a, err := aes.NewCipher(s.sessionEncKey)
if err != nil {
return nil, err
}
crypter := cipher.NewCBCEncrypter(a, s.iv)
crypter.CryptBlocks(ret, data)
return ret, nil
}
// pad applies message padding to a 16 byte boundary.
func pad(data []byte, terminator byte) []byte {
padded := make([]byte, (len(data)/16+1)*16)
copy(padded, data)
padded[len(data)] = terminator
return padded
}
// decryptAPDU is an internal method that decrypts and deserializes an APDU.
func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) {
a, err := aes.NewCipher(s.sessionEncKey)
if err != nil {
return nil, err
}
ret := make([]byte, len(data))
crypter := cipher.NewCBCDecrypter(a, s.iv)
crypter.CryptBlocks(ret, data)
return unpad(ret, 0x80)
}
// unpad strips padding from a message.
func unpad(data []byte, terminator byte) ([]byte, error) {
for i := 1; i <= 16; i++ {
switch data[len(data)-i] {
case 0:
continue
case terminator:
return data[:len(data)-i], nil
default:
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
}
}
return nil, errors.New("expected end of padding, got 0")
}
// updateIV is an internal method that updates the initialization vector after
// each message exchanged.
func (s *SecureChannelSession) updateIV(meta, data []byte) error {
data = pad(data, 0)
a, err := aes.NewCipher(s.sessionMacKey)
if err != nil {
return err
}
crypter := cipher.NewCBCEncrypter(a, make([]byte, 16))
crypter.CryptBlocks(meta, meta)
crypter.CryptBlocks(data, data)
// The first 16 bytes of the last block is the MAC
s.iv = data[len(data)-32 : len(data)-16]
return nil
}
File diff suppressed because it is too large Load Diff
-41
View File
@@ -1,41 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
// AccountsByURL implements sort.Interface for []Account based on the URL field.
type AccountsByURL []Account
func (a AccountsByURL) Len() int { return len(a) }
func (a AccountsByURL) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a AccountsByURL) Less(i, j int) bool { return a[i].URL.Cmp(a[j].URL) < 0 }
// WalletsByURL implements sort.Interface for []Wallet based on the URL field.
type WalletsByURL []Wallet
func (w WalletsByURL) Len() int { return len(w) }
func (w WalletsByURL) Swap(i, j int) { w[i], w[j] = w[j], w[i] }
func (w WalletsByURL) Less(i, j int) bool { return w[i].URL().Cmp(w[j].URL()) < 0 }
-113
View File
@@ -1,113 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
// URL represents the canonical identification URL of a wallet or account.
//
// It is a simplified version of url.URL, with the important limitations (which
// are considered features here) that it contains value-copyable components only,
// as well as that it doesn't do any URL encoding/decoding of special characters.
//
// The former is important to allow an account to be copied without leaving live
// references to the original version, whereas the latter is important to ensure
// one single canonical form opposed to many allowed ones by the RFC 3986 spec.
//
// As such, these URLs should not be used outside of the scope of an Ethereum
// wallet or account.
type URL struct {
Scheme string // Protocol scheme to identify a capable account backend
Path string // Path for the backend to identify a unique entity
}
// parseURL converts a user supplied URL into the accounts specific structure.
func parseURL(url string) (URL, error) {
parts := strings.Split(url, "://")
if len(parts) != 2 || parts[0] == "" {
return URL{}, errors.New("protocol scheme missing")
}
return URL{
Scheme: parts[0],
Path: parts[1],
}, nil
}
// String implements the stringer interface.
func (u URL) String() string {
if u.Scheme != "" {
return fmt.Sprintf("%s://%s", u.Scheme, u.Path)
}
return u.Path
}
// TerminalString implements the log.TerminalStringer interface.
func (u URL) TerminalString() string {
url := u.String()
if len(url) > 32 {
return url[:31] + ".."
}
return url
}
// MarshalJSON implements the json.Marshaller interface.
func (u URL) MarshalJSON() ([]byte, error) {
return json.Marshal(u.String())
}
// UnmarshalJSON parses url.
func (u *URL) UnmarshalJSON(input []byte) error {
var textURL string
err := json.Unmarshal(input, &textURL)
if err != nil {
return err
}
url, err := parseURL(textURL)
if err != nil {
return err
}
u.Scheme = url.Scheme
u.Path = url.Path
return nil
}
// Cmp compares x and y and returns:
//
// -1 if x < y
// 0 if x == y
// +1 if x > y
func (u URL) Cmp(url URL) int {
if u.Scheme == url.Scheme {
return strings.Compare(u.Path, url.Path)
}
return strings.Compare(u.Scheme, url.Scheme)
}
-112
View File
@@ -1,112 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package accounts
import (
"testing"
)
func TestURLParsing(t *testing.T) {
t.Parallel()
url, err := parseURL("https://ethereum.org")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if url.Scheme != "https" {
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
}
if url.Path != "ethereum.org" {
t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path)
}
for _, u := range []string{"ethereum.org", ""} {
if _, err = parseURL(u); err == nil {
t.Errorf("input %v, expected err, got: nil", u)
}
}
}
func TestURLString(t *testing.T) {
t.Parallel()
url := URL{Scheme: "https", Path: "ethereum.org"}
if url.String() != "https://ethereum.org" {
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
}
url = URL{Scheme: "", Path: "ethereum.org"}
if url.String() != "ethereum.org" {
t.Errorf("expected: %v, got: %v", "ethereum.org", url.String())
}
}
func TestURLMarshalJSON(t *testing.T) {
t.Parallel()
url := URL{Scheme: "https", Path: "ethereum.org"}
json, err := url.MarshalJSON()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if string(json) != "\"https://ethereum.org\"" {
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
}
}
func TestURLUnmarshalJSON(t *testing.T) {
t.Parallel()
url := &URL{}
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if url.Scheme != "https" {
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
}
if url.Path != "ethereum.org" {
t.Errorf("expected: %v, got: %v", "https", url.Path)
}
}
func TestURLComparison(t *testing.T) {
t.Parallel()
tests := []struct {
urlA URL
urlB URL
expect int
}{
{URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0},
{URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1},
{URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1},
{URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1},
}
for i, tt := range tests {
result := tt.urlA.Cmp(tt.urlB)
if result != tt.expect {
t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result)
}
}
}
+5 -6
View File
@@ -35,12 +35,12 @@ import (
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/triedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
@@ -169,12 +169,11 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
if pre.Env.BaseFee != nil {
vmContext.BaseFee = new(big.Int).Set(pre.Env.BaseFee)
}
// NOTE: this has been removed
// If random is defined, add it to the vmContext.
// if pre.Env.Random != nil {
// rnd := common.BigToHash(pre.Env.Random)
// vmContext.Random = &rnd
// }
if pre.Env.Random != nil {
rnd := common.BigToHash(pre.Env.Random)
vmContext.Random = &rnd
}
// Calculate the BlobBaseFee
var excessBlobGas uint64
if pre.Env.ExcessBlobGas != nil {
+1 -1
View File
@@ -30,8 +30,8 @@ import (
"fmt"
"strings"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/tests"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/urfave/cli/v2"
)
+1 -1
View File
@@ -21,9 +21,9 @@ import (
"io"
"math/big"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/eth/tracers"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
)
+2 -2
View File
@@ -143,7 +143,7 @@ func Transaction(ctx *cli.Context) error {
r.Address = sender
}
// Check intrinsic gas
rules := chainConfig.Rules(new(big.Int), 0)
rules := chainConfig.Rules(new(big.Int), params.IsMergeTODO, 0)
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, rules); err != nil {
r.Error = err
results = append(results, r)
@@ -176,7 +176,7 @@ func Transaction(ctx *cli.Context) error {
r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits")
}
// Check whether the init code size has been exceeded.
if chainConfig.IsDurango(0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
if params.GetExtra(chainConfig).IsDurango(0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
r.Error = errors.New("max initcode size exceeded")
}
results = append(results, r)
+3 -4
View File
@@ -37,13 +37,13 @@ import (
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/eth/tracers"
"github.com/ava-labs/subnet-evm/eth/tracers/logger"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/tests"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
@@ -204,8 +204,7 @@ func Transition(ctx *cli.Context) error {
}
func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
// NOTE: IsLondon replaced with IsSubnetEVM here
if !chainConfig.IsSubnetEVM(env.Timestamp) {
if !chainConfig.IsLondon(new(big.Int).SetUint64(env.Number)) {
return nil
}
// Sanity check, to not `panic` in state_transition
+2 -2
View File
@@ -38,8 +38,8 @@ import (
"github.com/urfave/cli/v2"
// Force-load the tracer engines to trigger registration
_ "github.com/ava-labs/subnet-evm/eth/tracers/js"
_ "github.com/ava-labs/subnet-evm/eth/tracers/native"
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
)
var (
+2 -2
View File
@@ -42,14 +42,14 @@ import (
"github.com/ava-labs/subnet-evm/core"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/core/vm/runtime"
"github.com/ava-labs/subnet-evm/eth/tracers/logger"
"github.com/ava-labs/subnet-evm/internal/flags"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/triedb"
"github.com/ava-labs/subnet-evm/triedb/hashdb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/urfave/cli/v2"
)
+2 -2
View File
@@ -34,10 +34,10 @@ import (
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/eth/tracers/logger"
"github.com/ava-labs/subnet-evm/tests"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/urfave/cli/v2"
)
+8 -7
View File
@@ -115,6 +115,7 @@ func (eng *DummyEngine) verifyCoinbase(config *params.ChainConfig, header *types
}
func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header *types.Header, parent *types.Header, chain consensus.ChainHeaderReader) error {
configExtra := params.GetExtra(config)
// Verify that the gas limit is <= 2^63-1
if header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
@@ -132,7 +133,7 @@ func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header
if err != nil {
return err
}
if config.IsSubnetEVM(header.Time) {
if configExtra.IsSubnetEVM(header.Time) {
expectedGasLimit := feeConfig.GasLimit.Uint64()
if header.GasLimit != expectedGasLimit {
return fmt.Errorf("expected gas limit to be %d, but found %d", expectedGasLimit, header.GasLimit)
@@ -201,16 +202,17 @@ func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header
// modified from consensus.go
func (eng *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, uncle bool) error {
config := chain.Config()
configExtra := params.GetExtra(config)
// Ensure that we do not verify an uncle
if uncle {
return errUnclesUnsupported
}
switch {
case config.IsDurango(header.Time):
case configExtra.IsDurango(header.Time):
if len(header.Extra) < params.DynamicFeeExtraDataSize {
return fmt.Errorf("expected extra-data field length >= %d, found %d", params.DynamicFeeExtraDataSize, len(header.Extra))
}
case config.IsSubnetEVM(header.Time):
case configExtra.IsSubnetEVM(header.Time):
if len(header.Extra) != params.DynamicFeeExtraDataSize {
return fmt.Errorf("expected extra-data field to be: %d, but found %d", params.DynamicFeeExtraDataSize, len(header.Extra))
}
@@ -363,16 +365,15 @@ func (eng *DummyEngine) verifyBlockFee(
}
func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types.Block, parent *types.Header, state *state.StateDB, receipts []*types.Receipt) error {
if chain.Config().IsSubnetEVM(block.Time()) {
if params.GetExtra(chain.Config()).IsSubnetEVM(block.Time()) {
// we use the parent to determine the fee config
// since the current block has not been finalized yet.
feeConfig, _, err := chain.GetFeeConfigAt(parent)
if err != nil {
return err
}
// Calculate the expected blockGasCost for this block.
// Note: this is a deterministic transtion that defines an exact block fee for this block.
// Note: this is a deterministic transition that defines an exact block fee for this block.
blockGasCost := calcBlockGasCost(
feeConfig.TargetBlockRate,
feeConfig.MinBlockGasCost,
@@ -402,7 +403,7 @@ func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types
func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt,
) (*types.Block, error) {
if chain.Config().IsSubnetEVM(header.Time) {
if params.GetExtra(chain.Config()).IsSubnetEVM(header.Time) {
// we use the parent to determine the fee config
// since the current block has not been finalized yet.
feeConfig, _, err := chain.GetFeeConfigAt(parent)
+3 -2
View File
@@ -22,7 +22,8 @@ import (
func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) {
// If the current block is the first EIP-1559 block, or it is the genesis block
// return the initial slice and initial base fee.
isSubnetEVM := config.IsSubnetEVM(parent.Time)
configExtra := params.GetExtra(config)
isSubnetEVM := configExtra.IsSubnetEVM(parent.Time)
extraDataSize := params.DynamicFeeExtraDataSize
if !isSubnetEVM || parent.Number.Cmp(common.Big0) == 0 {
@@ -248,7 +249,7 @@ func calcBlockGasCost(
//
// This function will return nil for all return values prior to Subnet EVM.
func MinRequiredTip(config *params.ChainConfig, header *types.Header) (*big.Int, error) {
if !config.IsSubnetEVM(header.Time) {
if !params.GetExtra(config).IsSubnetEVM(header.Time) {
return nil, nil
}
if header.BaseFee == nil {
+1 -1
View File
@@ -34,10 +34,10 @@ import (
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
)
+1 -1
View File
@@ -46,7 +46,6 @@ import (
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/state/snapshot"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/internal/version"
"github.com/ava-labs/subnet-evm/metrics"
"github.com/ava-labs/subnet-evm/params"
@@ -56,6 +55,7 @@ import (
"github.com/ava-labs/subnet-evm/triedb/pathdb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
+1 -1
View File
@@ -12,9 +12,9 @@ import (
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
+8 -8
View File
@@ -36,12 +36,12 @@ import (
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/state/snapshot"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/contracts/feemanager"
"github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager"
"github.com/ava-labs/subnet-evm/triedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/event"
)
@@ -365,9 +365,9 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e
// Otherwise returns the fee config in the chain config.
// Assumes that a valid configuration is stored when the precompile is activated.
func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) {
config := bc.Config()
if !config.IsPrecompileEnabled(feemanager.ContractAddress, parent.Time) {
return config.FeeConfig, common.Big0, nil
configExtra := params.GetExtra(bc.Config())
if !configExtra.IsPrecompileEnabled(feemanager.ContractAddress, parent.Time) {
return configExtra.FeeConfig, common.Big0, nil
}
// try to return it from the cache
@@ -399,13 +399,13 @@ func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig
// If RewardManager is activated at [parent], returns the reward manager config in the precompile contract state.
// If fee recipients are allowed, returns true in the second return value.
func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) {
config := bc.Config()
if !config.IsSubnetEVM(parent.Time) {
configExtra := params.GetExtra(bc.Config())
if !configExtra.IsSubnetEVM(parent.Time) {
return constants.BlackholeAddr, false, nil
}
if !config.IsPrecompileEnabled(rewardmanager.ContractAddress, parent.Time) {
if bc.chainConfig.AllowFeeRecipients {
if !configExtra.IsPrecompileEnabled(rewardmanager.ContractAddress, parent.Time) {
if configExtra.AllowFeeRecipients {
return common.Address{}, true, nil
} else {
return constants.BlackholeAddr, false, nil
+4 -4
View File
@@ -37,10 +37,10 @@ import (
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/triedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
@@ -527,14 +527,14 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
defer db.Close() // Might double close, should be fine
// Initialize a fresh chain
chainConfig := *params.TestChainConfig
chainConfig.FeeConfig.MinBaseFee = big.NewInt(1)
chainConfig := params.Copy(params.TestChainConfig)
params.GetExtra(&chainConfig).FeeConfig.MinBaseFee = big.NewInt(1)
var (
require = require.New(t)
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
gspec = &Genesis{
BaseFee: chainConfig.FeeConfig.MinBaseFee,
BaseFee: params.GetExtra(&chainConfig).FeeConfig.MinBaseFee,
Config: &chainConfig,
Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(params.Ether)}},
}
+1 -1
View File
@@ -42,9 +42,9 @@ import (
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
)
+12 -6
View File
@@ -15,11 +15,11 @@ import (
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/state/pruner"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/eth/tracers/logger"
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/holiman/uint256"
)
@@ -313,8 +313,11 @@ func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) {
// Ensure that key1 has some funds in the genesis block.
genesisBalance := big.NewInt(1000000)
gspec := &Genesis{
Config: &params.ChainConfig{HomesteadBlock: new(big.Int), FeeConfig: params.DefaultFeeConfig},
Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}},
Config: params.WithExtra(
&params.ChainConfig{HomesteadBlock: new(big.Int)},
&params.ChainConfigExtra{FeeConfig: params.DefaultFeeConfig},
),
Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}},
}
blockchain, err := createBlockChain(chainDB, pruningConfig, gspec, common.Hash{})
@@ -426,8 +429,11 @@ func TestUngracefulAsyncShutdown(t *testing.T) {
// Ensure that key1 has some funds in the genesis block.
genesisBalance := big.NewInt(1000000)
gspec := &Genesis{
Config: &params.ChainConfig{HomesteadBlock: new(big.Int), FeeConfig: params.DefaultFeeConfig},
Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}},
Config: params.WithExtra(
&params.ChainConfig{HomesteadBlock: new(big.Int)},
&params.ChainConfigExtra{FeeConfig: params.DefaultFeeConfig},
),
Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}},
}
blockchain, err := create(chainDB, gspec, common.Hash{})
+4 -4
View File
@@ -38,10 +38,10 @@ import (
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/triedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/holiman/uint256"
)
@@ -383,7 +383,7 @@ func (cm *chainMaker) makeHeader(parent *types.Block, gap uint64, state *state.S
Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: time,
}
if cm.config.IsSubnetEVM(time) {
if params.GetExtra(cm.config).IsSubnetEVM(time) {
feeConfig, _, err := cm.GetFeeConfigAt(parent.Header())
if err != nil {
panic(err)
@@ -495,9 +495,9 @@ func (cm *chainMaker) GetBlock(hash common.Hash, number uint64) *types.Block {
}
func (cm *chainMaker) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) {
return cm.config.FeeConfig, nil, nil
return params.GetExtra(cm.config).FeeConfig, nil, nil
}
func (cm *chainMaker) GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) {
return constants.BlackholeAddr, cm.config.AllowFeeRecipients, nil
return constants.BlackholeAddr, params.GetExtra(cm.config).AllowFeeRecipients, nil
}
+1 -1
View File
@@ -33,10 +33,10 @@ import (
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/triedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
)
+68 -19
View File
@@ -27,18 +27,58 @@
package core
import (
"bytes"
"math/big"
"github.com/ava-labs/subnet-evm/consensus"
"github.com/ava-labs/subnet-evm/consensus/misc/eip4844"
"github.com/ava-labs/subnet-evm/core/extstate"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/predicate"
"github.com/ethereum/go-ethereum/common"
gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
)
func init() {
vm.RegisterHooks(hooks{})
}
type hooks struct{}
// OverrideNewEVMArgs is a hook that is called back when a new EVM is created.
// It allows for the modification of the EVM arguments before the EVM is created.
// Specifically, we set Random to be the same as Difficulty since Shanghai.
// This allows using the same jump table as upstream.
// Then we set Difficulty to nil as it is post Merge in upstream.
// Additionally we wrap the StateDB with the appropriate StateDB wrapper,
// which is used in coreth to process historical pre-AP1 blocks with the
// GetCommittedState method as it was historically.
func (hooks) OverrideNewEVMArgs(args *vm.NewEVMArgs) *vm.NewEVMArgs {
rules := args.ChainConfig.Rules(args.BlockContext.BlockNumber, params.IsMergeTODO, args.BlockContext.Time)
args.StateDB = wrapStateDB(rules, args.StateDB)
if rules.IsShanghai {
args.BlockContext.Random = new(common.Hash)
args.BlockContext.Random.SetBytes(args.BlockContext.Difficulty.Bytes())
args.BlockContext.Difficulty = nil
}
return args
}
func (hooks) OverrideEVMResetArgs(rules params.Rules, args *vm.EVMResetArgs) *vm.EVMResetArgs {
args.StateDB = wrapStateDB(rules, args.StateDB)
return args
}
func wrapStateDB(rules params.Rules, db vm.StateDB) vm.StateDB {
return &extstate.StateDB{VmStateDB: db.(extstate.VmStateDB)}
}
// ChainContext supports retrieving headers and consensus parameters from the
// current blockchain to be used during transaction processing.
type ChainContext interface {
@@ -51,14 +91,14 @@ type ChainContext interface {
// NewEVMBlockContext creates a new context for use in the EVM.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
predicateBytes, ok := predicate.GetPredicateResultBytes(header.Extra)
if !ok {
predicateBytes := predicate.GetPredicateResultBytes(header.Extra)
if len(predicateBytes) == 0 {
return newEVMBlockContext(header, chain, author, nil)
}
// Prior to Durango, the VM enforces the extra data is smaller than or
// equal to this size. After Durango, the VM pre-verifies the extra
// data past the dynamic fee rollup window is valid.
predicateResults, err := predicate.ParseResults(predicateBytes)
_, err := predicate.ParseResults(predicateBytes)
if err != nil {
log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra)
// As mentioned above, we pre-verify the extra data to ensure this never happens.
@@ -66,18 +106,22 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
// as defense in depth.
return newEVMBlockContext(header, chain, author, nil)
}
return newEVMBlockContext(header, chain, author, predicateResults)
return newEVMBlockContext(header, chain, author, header.Extra)
}
// NewEVMBlockContextWithPredicateResults creates a new context for use in the EVM with an override for the predicate results that is not present
// in header.Extra.
// This function is used to create a BlockContext when the header Extra data is not fully formed yet and it's more efficient to pass in predicateResults
// directly rather than re-encode the latest results when executing each individaul transaction.
func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
return newEVMBlockContext(header, chain, author, predicateResults)
func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateBytes []byte) vm.BlockContext {
extra := bytes.Clone(header.Extra)
if len(predicateBytes) > 0 {
extra = predicate.SetPredicateResultBytes(extra, predicateBytes)
}
return newEVMBlockContext(header, chain, author, extra)
}
func newEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
func newEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, extra []byte) vm.BlockContext {
var (
beneficiary common.Address
baseFee *big.Int
@@ -96,18 +140,23 @@ func newEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.ExcessBlobGas != nil {
blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas)
}
return vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
PredicateResults: predicateResults,
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit,
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit,
Header: &gethtypes.Header{
Number: new(big.Int).Set(header.Number),
Time: header.Time,
Extra: extra,
},
}
}
+61
View File
@@ -0,0 +1,61 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package extstate
import (
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/predicate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
)
type VmStateDB interface {
vm.StateDB
GetTxHash() common.Hash
GetLogData() (topics [][]common.Hash, data [][]byte)
}
type StateDB struct {
VmStateDB
// Ordered storage slots to be used in predicate verification as set in the tx access list.
// Only set in PrepareAccessList, and un-modified through execution.
predicateStorageSlots map[common.Address][][]byte
}
func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
rulesExtra := params.GetRulesExtra(rules)
s.predicateStorageSlots = predicate.PreparePredicateStorageSlots(rulesExtra, list)
s.VmStateDB.Prepare(rules, sender, coinbase, dst, precompiles, list)
}
// GetPredicateStorageSlots returns the storage slots associated with the address, index pair.
// A list of access tuples can be included within transaction types post EIP-2930. The address
// is declared directly on the access tuple and the index is the i'th occurrence of an access
// tuple with the specified address.
//
// Ex. AccessList[[AddrA, Predicate1], [AddrB, Predicate2], [AddrA, Predicate3]]
// In this case, the caller could retrieve predicates 1-3 with the following calls:
// GetPredicateStorageSlots(AddrA, 0) -> Predicate1
// GetPredicateStorageSlots(AddrB, 0) -> Predicate2
// GetPredicateStorageSlots(AddrA, 1) -> Predicate3
func (s *StateDB) GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) {
predicates, exists := s.predicateStorageSlots[address]
if !exists {
return nil, false
}
if index >= len(predicates) {
return nil, false
}
return predicates[index], true
}
// SetPredicateStorageSlots sets the predicate storage slots for the given address
func (s *StateDB) SetPredicateStorageSlots(address common.Address, predicates [][]byte) {
if s.predicateStorageSlots == nil {
s.predicateStorageSlots = make(map[common.Address][][]byte)
}
s.predicateStorageSlots[address] = predicates
}
@@ -1,12 +1,12 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package state
package extstate
import (
"testing"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/precompile/contract"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
@@ -14,7 +14,7 @@ import (
func NewTestStateDB(t testing.TB) contract.StateDB {
db := rawdb.NewMemoryDatabase()
stateDB, err := New(common.Hash{}, NewDatabase(db), nil)
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
require.NoError(t, err)
return stateDB
return &StateDB{VmStateDB: statedb}
}
+1 -1
View File
@@ -8,10 +8,10 @@ import (
"math/big"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
)
var _ = (*genesisSpecMarshaling)(nil)
+6 -7
View File
@@ -182,7 +182,6 @@ func SetupGenesisBlock(
rawdb.WriteChainConfig(db, stored, newcfg)
return newcfg, stored, nil
}
storedcfg.SetEthUpgrades(storedcfg.NetworkUpgrades)
// Check config compatibility and write the config. Compatibility errors
// are returned to the caller unless we're already at block zero.
// we use last accepted block for cfg compatibility check. Note this allows
@@ -202,8 +201,8 @@ func SetupGenesisBlock(
} else {
compatErr := storedcfg.CheckCompatible(newcfg, height, timestamp)
if compatErr != nil && ((height != 0 && compatErr.RewindToBlock != 0) || (timestamp != 0 && compatErr.RewindToTime != 0)) {
storedData, _ := storedcfg.ToWithUpgradesJSON().MarshalJSON()
newData, _ := newcfg.ToWithUpgradesJSON().MarshalJSON()
storedData, _ := params.ToWithUpgradesJSON(storedcfg).MarshalJSON()
newData, _ := params.ToWithUpgradesJSON(newcfg).MarshalJSON()
log.Error("found mismatch between config on database vs. new config", "storedConfig", string(storedData), "newConfig", string(newData), "err", compatErr)
return newcfg, stored, compatErr
}
@@ -304,11 +303,11 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo
}
if conf := g.Config; conf != nil {
num := new(big.Int).SetUint64(g.Number)
if conf.IsSubnetEVM(g.Timestamp) {
if params.GetExtra(conf).IsSubnetEVM(g.Timestamp) {
if g.BaseFee != nil {
head.BaseFee = g.BaseFee
} else {
head.BaseFee = new(big.Int).Set(g.Config.FeeConfig.MinBaseFee)
head.BaseFee = new(big.Int).Set(params.GetExtra(g.Config).FeeConfig.MinBaseFee)
}
}
if conf.IsCancun(num, g.Timestamp) {
@@ -377,7 +376,7 @@ func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.
func (g *Genesis) Verify() error {
// Make sure genesis gas limit is consistent
gasLimitConfig := g.Config.FeeConfig.GasLimit.Uint64()
gasLimitConfig := params.GetExtra(g.Config).FeeConfig.GasLimit.Uint64()
if gasLimitConfig != g.GasLimit {
return fmt.Errorf(
"gas limit in fee config (%d) does not match gas limit in header (%d)",
@@ -386,7 +385,7 @@ func (g *Genesis) Verify() error {
)
}
// Verify config
if err := g.Config.Verify(); err != nil {
if err := params.GetExtra(g.Config).Verify(); err != nil {
return err
}
return nil
+15 -15
View File
@@ -37,7 +37,6 @@ import (
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/allowlist"
"github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist"
@@ -47,6 +46,7 @@ import (
"github.com/ava-labs/subnet-evm/utils"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -70,8 +70,8 @@ func TestSetupGenesis(t *testing.T) {
}
func testSetupGenesis(t *testing.T, scheme string) {
preSubnetConfig := *params.TestPreSubnetEVMChainConfig
preSubnetConfig.SubnetEVMTimestamp = utils.NewUint64(100)
preSubnetConfig := params.Copy(params.TestPreSubnetEVMChainConfig)
params.GetExtra(&preSubnetConfig).SubnetEVMTimestamp = utils.NewUint64(100)
var (
customghash = common.HexToHash("0x4a12fe7bf8d40d152d7e9de22337b115186a4662aa3a97217b36146202bbfc66")
customg = Genesis{
@@ -79,13 +79,13 @@ func testSetupGenesis(t *testing.T, scheme string) {
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: preSubnetConfig.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(&preSubnetConfig).FeeConfig.GasLimit.Uint64(),
}
oldcustomg = customg
)
rollbackpreSubnetConfig := preSubnetConfig
rollbackpreSubnetConfig.SubnetEVMTimestamp = utils.NewUint64(90)
rollbackpreSubnetConfig := params.Copy(&preSubnetConfig)
params.GetExtra(&rollbackpreSubnetConfig).SubnetEVMTimestamp = utils.NewUint64(90)
oldcustomg.Config = &rollbackpreSubnetConfig
tests := []struct {
@@ -208,8 +208,8 @@ func TestStatefulPrecompilesConfigure(t *testing.T) {
for name, test := range map[string]test{
"allow list enabled in genesis": {
getConfig: func() *params.ChainConfig {
config := *params.TestChainConfig
config.GenesisPrecompiles = params.Precompiles{
config := params.Copy(params.TestChainConfig)
params.GetExtra(&config).GenesisPrecompiles = params.Precompiles{
deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(0), []common.Address{addr}, nil, nil),
}
return &config
@@ -228,7 +228,7 @@ func TestStatefulPrecompilesConfigure(t *testing.T) {
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: config.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(config).FeeConfig.GasLimit.Uint64(),
}
db := rawdb.NewMemoryDatabase()
@@ -261,7 +261,7 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) {
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(params.TestChainConfig).FeeConfig.GasLimit.Uint64(),
}
bc, _ := NewBlockChain(db, DefaultCacheConfig, &customg, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false)
defer bc.Stop()
@@ -283,9 +283,9 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) {
// header must be bigger than last accepted
require.Greater(block.Time, bc.lastAccepted.Time())
activatedGenesisConfig := *customg.Config
activatedGenesisConfig := params.Copy(customg.Config)
contractDeployerConfig := deployerallowlist.NewConfig(utils.NewUint64(51), nil, nil, nil)
activatedGenesisConfig.UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{
params.GetExtra(&activatedGenesisConfig).UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{
{
Config: contractDeployerConfig,
},
@@ -307,13 +307,13 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) {
func TestGenesisWriteUpgradesRegression(t *testing.T) {
require := require.New(t)
config := *params.TestChainConfig
config := params.Copy(params.TestChainConfig)
genesis := &Genesis{
Config: &config,
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: config.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(&config).FeeConfig.GasLimit.Uint64(),
}
db := rawdb.NewMemoryDatabase()
@@ -323,7 +323,7 @@ func TestGenesisWriteUpgradesRegression(t *testing.T) {
_, _, err := SetupGenesisBlock(db, trieDB, genesis, genesisBlock.Hash(), false)
require.NoError(err)
genesis.Config.UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{
params.GetExtra(genesis.Config).UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{
{
Config: deployerallowlist.NewConfig(utils.NewUint64(51), nil, nil, nil),
},
+1 -1
View File
@@ -36,9 +36,9 @@ import (
"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
)
func verifyUnbrokenCanonchain(bc *BlockChain) error {
+4 -2
View File
@@ -30,14 +30,15 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred
return nil, fmt.Errorf("%w for predicate verification (%d) < intrinsic gas (%d)", ErrIntrinsicGas, tx.Gas(), intrinsicGas)
}
rulesExtra := params.GetRulesExtra(rules)
predicateResults := make(map[common.Address][]byte)
// Short circuit early if there are no precompile predicates to verify
if !rules.PredicatersExist() {
if !rulesExtra.PredicatersExist() {
return predicateResults, nil
}
// Prepare the predicate storage slots from the transaction's access list
predicateArguments := predicate.PreparePredicateStorageSlots(rules, tx.AccessList())
predicateArguments := predicate.PreparePredicateStorageSlots(rulesExtra, tx.AccessList())
// If there are no predicates to verify, return early and skip requiring the proposervm block
// context to be populated.
@@ -52,6 +53,7 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred
for address, predicates := range predicateArguments {
// Since [address] is only added to [predicateArguments] when there's a valid predicate in the ruleset
// there's no need to check if the predicate exists here.
rules := params.GetRulesExtra(rules)
predicaterContract := rules.Predicaters[address]
bitset := set.NewBits()
for i, predicate := range predicates {
+6 -4
View File
@@ -297,9 +297,10 @@ func TestCheckPredicate(t *testing.T) {
t.Run(name, func(t *testing.T) {
require := require.New(t)
// Create the rules from TestChainConfig and update the predicates based on the test params
rules := params.TestChainConfig.Rules(common.Big0, 0)
rules := params.TestChainConfig.Rules(common.Big0, params.IsMergeTODO, 0)
if test.createPredicates != nil {
for address, predicater := range test.createPredicates(t) {
rules := params.GetRulesExtra(rules)
rules.Predicaters[address] = predicater
}
}
@@ -423,7 +424,7 @@ func TestCheckPredicatesOutput(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
// Create the rules from TestChainConfig and update the predicates based on the test params
rules := params.TestChainConfig.Rules(common.Big0, 0)
rules := params.TestChainConfig.Rules(common.Big0, params.IsMergeTODO, 0)
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
predicater.EXPECT().PredicateGas(gomock.Any()).Return(uint64(0), nil).Times(len(test.testTuple))
@@ -445,8 +446,9 @@ func TestCheckPredicatesOutput(t *testing.T) {
})
}
rules.Predicaters[addr1] = predicater
rules.Predicaters[addr2] = predicater
rulesExtra := params.GetRulesExtra(rules)
rulesExtra.Predicaters[addr1] = predicater
rulesExtra.Predicaters[addr2] = predicater
// Specify only the access list, since this test should not depend on any other values
tx := types.NewTx(&types.DynamicFeeTx{
+2 -2
View File
@@ -80,7 +80,7 @@ func ReadChainConfig(db ethdb.KeyValueReader, hash common.Hash) *params.ChainCon
if len(data) == 0 {
return &config // return early if no upgrade config is found
}
if err := json.Unmarshal(data, &config.UpgradeConfig); err != nil {
if err := json.Unmarshal(data, &params.GetExtra(&config).UpgradeConfig); err != nil {
log.Error("Invalid upgrade config JSON", "err", err)
return nil
}
@@ -102,7 +102,7 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
}
// Write the upgrade config for this chain config
data, err = json.Marshal(cfg.UpgradeConfig)
data, err = json.Marshal(params.GetExtra(cfg).UpgradeConfig)
if err != nil {
log.Crit("Failed to JSON encode upgrade config", "err", err)
}
+21 -74
View File
@@ -36,14 +36,13 @@ import (
"github.com/ava-labs/subnet-evm/core/state/snapshot"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/metrics"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/predicate"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/trie/trienode"
"github.com/ava-labs/subnet-evm/trie/triestate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@@ -117,9 +116,6 @@ type StateDB struct {
// Per-transaction access list
accessList *accessList
// Ordered storage slots to be used in predicate verification as set in the tx access list.
// Only set in PrepareAccessList, and un-modified through execution.
predicateStorageSlots map[common.Address][][]byte
// Transient storage
transientStorage transientStorage
@@ -172,24 +168,23 @@ func NewWithSnapshot(root common.Hash, db Database, snap snapshot.Snapshot) (*St
return nil, err
}
sdb := &StateDB{
db: db,
trie: tr,
originalRoot: root,
accounts: make(map[common.Hash][]byte),
storages: make(map[common.Hash]map[common.Hash][]byte),
accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
predicateStorageSlots: make(map[common.Address][][]byte),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
db: db,
trie: tr,
originalRoot: root,
accounts: make(map[common.Hash][]byte),
storages: make(map[common.Hash]map[common.Hash][]byte),
accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
}
if snap != nil {
if snap.Root() != root {
@@ -237,13 +232,7 @@ func (s *StateDB) Error() error {
// AddLog adds a log with the specified parameters to the statedb
// Note: blockNumber is a required argument because StateDB does not
// know the current block number.
func (s *StateDB) AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) {
log := &types.Log{
Address: addr,
Topics: topics,
Data: data,
BlockNumber: blockNumber,
}
func (s *StateDB) AddLog(log *types.Log) {
s.journal.append(addLogChange{txhash: s.thash})
log.TxHash = s.thash
@@ -735,19 +724,6 @@ func (s *StateDB) CreateAccount(addr common.Address) {
}
}
// copyPredicateStorageSlots creates a deep copy of the provided predicateStorageSlots map.
func copyPredicateStorageSlots(predicateStorageSlots map[common.Address][][]byte) map[common.Address][][]byte {
res := make(map[common.Address][][]byte, len(predicateStorageSlots))
for address, predicates := range predicateStorageSlots {
copiedPredicates := make([][]byte, len(predicates))
for i, predicateBytes := range predicates {
copiedPredicates[i] = common.CopyBytes(predicateBytes)
}
res[address] = copiedPredicates
}
return res
}
// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB {
@@ -841,7 +817,6 @@ func (s *StateDB) Copy() *StateDB {
// in the middle of a transaction.
state.accessList = s.accessList.Copy()
state.transientStorage = s.transientStorage.Copy()
state.predicateStorageSlots = copyPredicateStorageSlots(s.predicateStorageSlots)
// If there's a prefetcher running, make an inactive copy of it that can
// only access data but does not actively preload (since the user will not
@@ -1378,7 +1353,7 @@ func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot.
// - Add coinbase to access list (EIP-3651/Durango)
// - Reset transient storage (EIP-1153)
func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
if rules.IsSubnetEVM {
if rules.IsBerlin {
// Clear out any leftover from previous executions
al := newAccessList()
s.accessList = al
@@ -1397,11 +1372,9 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
al.AddSlot(el.Address, key)
}
}
if rules.IsDurango { // EIP-3651: warm coinbase
if rules.IsShanghai { // EIP-3651: warm coinbase
al.AddAddress(coinbase)
}
s.predicateStorageSlots = predicate.PreparePredicateStorageSlots(rules, list)
}
// Reset transient storage at the beginning of transaction execution
s.transientStorage = newTransientStorage()
@@ -1447,27 +1420,6 @@ func (s *StateDB) GetTxHash() common.Hash {
return s.thash
}
// GetPredicateStorageSlots returns the storage slots associated with the address, index pair.
// A list of access tuples can be included within transaction types post EIP-2930. The address
// is declared directly on the access tuple and the index is the i'th occurrence of an access
// tuple with the specified address.
//
// Ex. AccessList[[AddrA, Predicate1], [AddrB, Predicate2], [AddrA, Predicate3]]
// In this case, the caller could retrieve predicates 1-3 with the following calls:
// GetPredicateStorageSlots(AddrA, 0) -> Predicate1
// GetPredicateStorageSlots(AddrB, 0) -> Predicate2
// GetPredicateStorageSlots(AddrA, 1) -> Predicate3
func (s *StateDB) GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) {
predicates, exists := s.predicateStorageSlots[address]
if !exists {
return nil, false
}
if index >= len(predicates) {
return nil, false
}
return predicates[index], true
}
// convertAccountSet converts a provided account set from address keyed to hash keyed.
func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount) map[common.Hash]struct{} {
ret := make(map[common.Hash]struct{}, len(set))
@@ -1482,11 +1434,6 @@ func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount)
return ret
}
// SetPredicateStorageSlots sets the predicate storage slots for the given address
func (s *StateDB) SetPredicateStorageSlots(address common.Address, predicates [][]byte) {
s.predicateStorageSlots[address] = predicates
}
// copySet returns a deep-copied set.
func copySet[k comparable](set map[k][]byte) map[k][]byte {
copied := make(map[k][]byte, len(set))
+1 -1
View File
@@ -339,7 +339,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
fn: func(a testAction, s *StateDB) {
data := make([]byte, 2)
binary.BigEndian.PutUint16(data, uint16(a.args[0]))
s.AddLog(addr, nil, data, 0)
s.AddLog(&types.Log{Address: addr, Data: data})
},
args: make([]int64, 1),
},
+23 -4
View File
@@ -32,14 +32,15 @@ import (
"math/big"
"github.com/ava-labs/subnet-evm/consensus"
"github.com/ava-labs/subnet-evm/core/extstate"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/contract"
"github.com/ava-labs/subnet-evm/precompile/modules"
"github.com/ava-labs/subnet-evm/stateupgrade"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)
@@ -214,8 +215,9 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64,
// This ensures that the order we call Configure for each precompile is consistent.
// This ensures even if precompiles read/write state other than their own they will observe
// an identical global state in a deterministic order when they are configured.
extra := params.GetExtra(c)
for _, module := range modules.RegisteredModules() {
for _, activatingConfig := range c.GetActivatingPrecompileConfigs(module.Address, parentTimestamp, blockTimestamp, c.PrecompileUpgrades) {
for _, activatingConfig := range extra.GetActivatingPrecompileConfigs(module.Address, parentTimestamp, blockTimestamp, extra.PrecompileUpgrades) {
// If this transition activates the upgrade, configure the stateful precompile.
// (or deconfigure it if it is being disabled.)
if activatingConfig.IsDisabled() {
@@ -243,7 +245,8 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64,
// can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure
// that it does not attempt to invoke a non-existent contract.
statedb.SetCode(module.Address, []byte{0x1})
if err := module.Configure(c, activatingConfig, statedb, blockContext); err != nil {
extstatedb := &extstate.StateDB{VmStateDB: statedb}
if err := module.Configure(params.GetExtra(c), activatingConfig, extstatedb, blockContext); err != nil {
return fmt.Errorf("could not configure precompile, name: %s, reason: %w", module.ConfigKey, err)
}
}
@@ -257,7 +260,8 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64,
// to apply the necessary state transitions for the upgrade.
func applyStateUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
// Apply state upgrades
for _, upgrade := range c.GetActivatingStateUpgrades(parentTimestamp, blockContext.Timestamp(), c.StateUpgrades) {
configExtra := params.GetExtra(c)
for _, upgrade := range configExtra.GetActivatingStateUpgrades(parentTimestamp, blockContext.Timestamp(), configExtra.StateUpgrades) {
log.Info("Applying state upgrade", "blockNumber", blockContext.Number(), "upgrade", upgrade)
if err := stateupgrade.Configure(&upgrade, c, statedb, blockContext); err != nil {
return fmt.Errorf("could not configure state upgrade: %w", err)
@@ -278,3 +282,18 @@ func ApplyUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext
}
return applyStateUpgrades(c, parentTimestamp, blockContext, statedb)
}
type blockContext struct {
number *big.Int
timestamp uint64
}
func NewBlockContext(number *big.Int, timestamp uint64) *blockContext {
return &blockContext{
number: number,
timestamp: timestamp,
}
}
func (bc *blockContext) Number() *big.Int { return bc.number }
func (bc *blockContext) Timestamp() uint64 { return bc.timestamp }
+47 -39
View File
@@ -37,12 +37,12 @@ import (
"github.com/ava-labs/subnet-evm/consensus/misc/eip4844"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
@@ -55,11 +55,11 @@ func u64(val uint64) *uint64 { return &val }
// blockchain imports bad blocks, meaning blocks which have valid headers but
// contain invalid transactions
func TestStateProcessorErrors(t *testing.T) {
cpcfg := *params.TestChainConfig
cpcfg := params.Copy(params.TestChainConfig)
config := &cpcfg
config.ShanghaiTime = u64(0)
config.CancunTime = u64(0)
config.FeeConfig.MinBaseFee = big.NewInt(params.TestMaxBaseFee)
params.GetExtra(config).FeeConfig.MinBaseFee = big.NewInt(params.TestMaxBaseFee)
var (
signer = types.LatestSigner(config)
@@ -120,7 +120,7 @@ func TestStateProcessorErrors(t *testing.T) {
Nonce: 0,
},
},
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(params.TestChainConfig).FeeConfig.GasLimit.Uint64(),
}
// FullFaker used to skip header verification that enforces no blobs.
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false)
@@ -258,26 +258,28 @@ func TestStateProcessorErrors(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: &params.ChainConfig{
ChainID: big.NewInt(1),
FeeConfig: params.DefaultFeeConfig,
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
},
Config: params.WithExtra(
&params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
},
&params.ChainConfigExtra{FeeConfig: params.DefaultFeeConfig},
),
Alloc: types.GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): types.Account{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(params.TestChainConfig).FeeConfig.GasLimit.Uint64(),
}
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false)
)
@@ -317,7 +319,7 @@ func TestStateProcessorErrors(t *testing.T) {
Code: common.FromHex("0xB0B0FACE"),
},
},
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(params.TestChainConfig).FeeConfig.GasLimit.Uint64(),
}
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false)
)
@@ -353,25 +355,31 @@ func TestBadTxAllowListBlock(t *testing.T) {
db = rawdb.NewMemoryDatabase()
testAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
config = &params.ChainConfig{
ChainID: big.NewInt(1),
FeeConfig: params.DefaultFeeConfig,
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
NetworkUpgrades: params.NetworkUpgrades{
SubnetEVMTimestamp: utils.NewUint64(0),
config = params.WithExtra(
&params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
},
GenesisPrecompiles: params.Precompiles{
txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(0), nil, nil, nil),
&params.ChainConfigExtra{
FeeConfig: params.DefaultFeeConfig,
NetworkUpgrades: params.NetworkUpgrades{
SubnetEVMTimestamp: utils.NewUint64(0),
},
GenesisPrecompiles: params.Precompiles{
txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(0), nil, nil, nil),
},
},
}
)
signer = types.LatestSigner(config)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@@ -383,7 +391,7 @@ func TestBadTxAllowListBlock(t *testing.T) {
Nonce: 0,
},
},
GasLimit: config.FeeConfig.GasLimit.Uint64(),
GasLimit: params.GetExtra(config).FeeConfig.GasLimit.Uint64(),
}
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false)
)
@@ -445,8 +453,8 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
UncleHash: types.EmptyUncleHash,
}
if config.IsSubnetEVM(header.Time) {
header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, config.FeeConfig, parent.Header(), header.Time)
if params.GetExtra(config).IsSubnetEVM(header.Time) {
header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, params.GetExtra(config).FeeConfig, parent.Header(), header.Time)
header.BlockGasCost = big.NewInt(0)
}
var receipts []*types.Receipt
+26 -10
View File
@@ -31,14 +31,16 @@ import (
"math"
"math/big"
"github.com/ava-labs/subnet-evm/constants"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist"
"github.com/ava-labs/subnet-evm/precompile/modules"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ava-labs/subnet-evm/vmerrs"
"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"
)
@@ -114,7 +116,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
}
gas += z * params.TxDataZeroGas
if isContractCreation && rules.IsDurango {
if isContractCreation && params.GetRulesExtra(rules).IsDurango {
lenWords := toWordSize(dataLen)
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
return 0, ErrGasUintOverflow
@@ -139,7 +141,8 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, error) {
var gas uint64
if !rules.PredicatersExist() {
rulesExtra := params.GetRulesExtra(rules)
if !rulesExtra.PredicatersExist() {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
return gas, nil
@@ -147,7 +150,7 @@ func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, err
for _, accessTuple := range accessList {
address := accessTuple.Address
predicaterContract, ok := rules.Predicaters[address]
predicaterContract, ok := rulesExtra.Predicaters[address]
if !ok {
// Previous access list gas calculation does not use safemath because an overflow would not be possible with
// the size of access lists that could be included in a block and standard access list gas costs.
@@ -330,6 +333,18 @@ func (st *StateTransition) buyGas() error {
return nil
}
// TODO: Probably can be removed -- extremely unlikely someone has private keys
// to known hashes.
// IsProhibited returns true if [addr] is in the prohibited list of addresses which should
// not be allowed as an EOA or newly created contract address.
func IsProhibited(addr common.Address) bool {
if addr == constants.BlackholeAddr {
return true
}
return modules.ReservedAddress(addr)
}
func (st *StateTransition) preCheck() error {
// Only check transactions that are not fake
msg := st.msg
@@ -353,12 +368,12 @@ func (st *StateTransition) preCheck() error {
msg.From.Hex(), codeHash)
}
// Make sure the sender is not prohibited
if vm.IsProhibited(msg.From) {
if IsProhibited(msg.From) {
return fmt.Errorf("%w: address %v", vmerrs.ErrAddrProhibited, msg.From)
}
// Check that the sender is on the tx allow list if enabled
if st.evm.ChainConfig().IsPrecompileEnabled(txallowlist.ContractAddress, st.evm.Context.Time) {
if params.GetExtra(st.evm.ChainConfig()).IsPrecompileEnabled(txallowlist.ContractAddress, st.evm.Context.Time) {
txAllowListRole := txallowlist.GetTxAllowListStatus(st.state, msg.From)
if !txAllowListRole.IsEnabled() {
return fmt.Errorf("%w: %s", vmerrs.ErrSenderAddressNotAllowListed, msg.From)
@@ -366,7 +381,7 @@ func (st *StateTransition) preCheck() error {
}
}
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
if st.evm.ChainConfig().IsSubnetEVM(st.evm.Context.Time) {
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
if !skipCheck {
@@ -462,7 +477,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
var (
msg = st.msg
sender = vm.AccountRef(msg.From)
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Time)
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, params.IsMergeTODO, st.evm.Context.Time)
rulesExtra = params.GetRulesExtra(rules)
contractCreation = msg.To == nil
)
@@ -486,7 +502,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
// Check whether the init code size has been exceeded.
if rules.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
if rulesExtra.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
return nil, fmt.Errorf("%w: code size %v limit %v", vmerrs.ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
}
@@ -510,7 +526,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if overflow {
return nil, ErrGasUintOverflow
}
gasRefund := st.refundGas(rules.IsSubnetEVM)
gasRefund := st.refundGas(rulesExtra.IsSubnetEVM)
fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, price)
st.state.AddBalance(st.evm.Context.Coinbase, fee)
+3 -3
View File
@@ -1483,9 +1483,9 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, gspec
// Ensure that key1 has sufficient funds in the genesis block for all of the tests.
genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether))
config := *params.TestChainConfig
config := params.Copy(params.TestChainConfig)
// Set all of the required config parameters
config.GenesisPrecompiles = params.Precompiles{
params.GetExtra(&config).GenesisPrecompiles = params.Precompiles{
deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(0), []common.Address{addr1}, nil, nil),
feemanager.ConfigKey: feemanager.NewConfig(utils.NewUint64(0), []common.Address{addr1}, nil, nil, nil),
}
@@ -1611,7 +1611,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, gspec
feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.Genesis().Header())
assert.NoError(err)
assert.EqualValues(config.FeeConfig, feeConfig)
assert.EqualValues(params.GetExtra(&config).FeeConfig, feeConfig)
},
},
}
+4 -4
View File
@@ -72,8 +72,8 @@ var testChainConfig *params.ChainConfig
func init() {
testChainConfig = new(params.ChainConfig)
*testChainConfig = *params.TestChainConfig
testChainConfig.FeeConfig.MinBaseFee = new(big.Int).SetUint64(1)
*testChainConfig = params.Copy(params.TestChainConfig)
params.GetExtra(testChainConfig).FeeConfig.MinBaseFee = new(big.Int).SetUint64(1)
testChainConfig.CancunTime = new(uint64)
*testChainConfig.CancunTime = uint64(time.Now().Unix())
@@ -117,7 +117,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
Extra: make([]byte, params.DynamicFeeExtraDataSize),
}
_, baseFee, err := dummy.CalcBaseFee(
bc.config, bc.config.FeeConfig, parent, blockTime,
bc.config, params.GetExtra(bc.config).FeeConfig, parent, blockTime,
)
if err != nil {
panic(err)
@@ -172,7 +172,7 @@ func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
}
func (bc *testBlockChain) GetFeeConfigAt(header *types.Header) (commontype.FeeConfig, *big.Int, error) {
return bc.config.FeeConfig, nil, nil
return params.GetExtra(bc.config).FeeConfig, nil, nil
}
// makeAddressReserver is a utility method to sanity check that accounts are
+6 -5
View File
@@ -695,6 +695,7 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
State: pool.currentState,
Rules: pool.chainconfig.Rules(
pool.currentHead.Load().Number,
params.IsMergeTODO,
pool.currentHead.Load().Time,
),
MinimumFee: pool.minimumFee,
@@ -1372,7 +1373,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
if reset != nil {
pool.demoteUnexecutables()
if reset.newHead != nil {
if pool.chainconfig.IsSubnetEVM(reset.newHead.Time) {
if pool.chainconfig.IsLondon(reset.newHead.Number) {
if err := pool.updateBaseFeeAt(reset.newHead); err != nil {
log.Error("error at updating base fee in tx pool", "error", err)
}
@@ -1510,7 +1511,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
// when we reset txPool we should explicitly check if fee struct for min base fee has changed
// so that we can correctly drop txs with < minBaseFee from tx pool.
if pool.chainconfig.IsPrecompileEnabled(feemanager.ContractAddress, newHead.Time) {
if params.GetExtra(pool.chainconfig).IsPrecompileEnabled(feemanager.ContractAddress, newHead.Time) {
feeConfig, _, err := pool.chain.GetFeeConfigAt(newHead)
if err != nil {
log.Error("Failed to get fee config state", "err", err, "root", newHead.Root)
@@ -1796,13 +1797,13 @@ func (pool *LegacyPool) demoteUnexecutables() {
}
func (pool *LegacyPool) startPeriodicFeeUpdate() {
if pool.chainconfig.SubnetEVMTimestamp == nil {
if params.GetExtra(pool.chainconfig).SubnetEVMTimestamp == nil {
return
}
// Call updateBaseFee here to ensure that there is not a [baseFeeUpdateInterval] delay
// when starting up in Subnet EVM before the base fee is updated.
if time.Now().After(utils.Uint64ToTime(pool.chainconfig.SubnetEVMTimestamp)) {
if time.Now().After(utils.Uint64ToTime(params.GetExtra(pool.chainconfig).SubnetEVMTimestamp)) {
pool.updateBaseFee()
}
@@ -1815,7 +1816,7 @@ func (pool *LegacyPool) periodicBaseFeeUpdate() {
// Sleep until its time to start the periodic base fee update or the tx pool is shutting down
select {
case <-time.After(time.Until(utils.Uint64ToTime(pool.chainconfig.SubnetEVMTimestamp))):
case <-time.After(time.Until(utils.Uint64ToTime(params.GetExtra(pool.chainconfig).SubnetEVMTimestamp))):
case <-pool.generalShutdownChan:
return // Return early if shutting down
}
+2 -2
View File
@@ -47,7 +47,6 @@ import (
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
@@ -79,7 +78,8 @@ var (
func init() {
cpy := *params.TestChainConfig
eip1559Config = &cpy
eip1559Config.SubnetEVMTimestamp = utils.NewUint64(0)
eip1559Config.BerlinBlock = common.Big0
eip1559Config.LondonBlock = common.Big0
}
type testBlockChain struct {
+5 -5
View File
@@ -75,17 +75,17 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize)
}
// Ensure only transactions that have been enabled are accepted
if !opts.Config.IsSubnetEVM(head.Time) && tx.Type() != types.LegacyTxType {
if !opts.Config.IsBerlin(head.Number) && tx.Type() != types.LegacyTxType {
return fmt.Errorf("%w: type %d rejected, pool not yet in Berlin", core.ErrTxTypeNotSupported, tx.Type())
}
if !opts.Config.IsSubnetEVM(head.Time) && tx.Type() == types.DynamicFeeTxType {
if !opts.Config.IsLondon(head.Number) && tx.Type() == types.DynamicFeeTxType {
return fmt.Errorf("%w: type %d rejected, pool not yet in London", core.ErrTxTypeNotSupported, tx.Type())
}
if !opts.Config.IsCancun(head.Number, head.Time) && tx.Type() == types.BlobTxType {
return fmt.Errorf("%w: type %d rejected, pool not yet in Cancun", core.ErrTxTypeNotSupported, tx.Type())
}
// Check whether the init code size has been exceeded
if opts.Config.IsDurango(head.Time) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
if opts.Config.IsShanghai(head.Number, head.Time) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
return fmt.Errorf("%w: code size %v, limit %v", vmerrs.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
}
// Transactions can't be negative. This may never happen using RLP decoded
@@ -120,7 +120,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, opts.Config.Rules(head.Number, head.Time))
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, opts.Config.Rules(head.Number, params.IsMergeTODO, head.Time))
if err != nil {
return err
}
@@ -274,7 +274,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
}
// If the tx allow list is enabled, return an error if the from address is not allow listed.
if opts.Rules.IsPrecompileEnabled(txallowlist.ContractAddress) {
if params.GetRulesExtra(opts.Rules).IsPrecompileEnabled(txallowlist.ContractAddress) {
txAllowListRole := txallowlist.GetTxAllowListStatus(opts.State, from)
if !txAllowListRole.IsEnabled() {
return fmt.Errorf("%w: %s", vmerrs.ErrSenderAddressNotAllowListed, from)
+1 -1
View File
@@ -29,7 +29,7 @@ package core
import (
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ethereum/go-ethereum/core/vm"
)
// Validator is an interface which defines the standard for block validation. It
+1 -1
View File
@@ -33,10 +33,10 @@ import (
"testing"
"github.com/ava-labs/subnet-evm/internal/blocktest"
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
-170
View File
@@ -1,170 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"encoding/binary"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
type bytesBacked interface {
Bytes() []byte
}
const (
// BloomByteLength represents the number of bytes used in a header log bloom.
BloomByteLength = 256
// BloomBitLength represents the number of bits used in a header log bloom.
BloomBitLength = 8 * BloomByteLength
)
// Bloom represents a 2048 bit bloom filter.
type Bloom [BloomByteLength]byte
// BytesToBloom converts a byte slice to a bloom filter.
// It panics if b is not of suitable size.
func BytesToBloom(b []byte) Bloom {
var bloom Bloom
bloom.SetBytes(b)
return bloom
}
// SetBytes sets the content of b to the given bytes.
// It panics if d is not of suitable size.
func (b *Bloom) SetBytes(d []byte) {
if len(b) < len(d) {
panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d)))
}
copy(b[BloomByteLength-len(d):], d)
}
// Add adds d to the filter. Future calls of Test(d) will return true.
func (b *Bloom) Add(d []byte) {
b.add(d, make([]byte, 6))
}
// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes)
func (b *Bloom) add(d []byte, buf []byte) {
i1, v1, i2, v2, i3, v3 := bloomValues(d, buf)
b[i1] |= v1
b[i2] |= v2
b[i3] |= v3
}
// Big converts b to a big integer.
// Note: Converting a bloom filter to a big.Int and then calling GetBytes
// does not return the same bytes, since big.Int will trim leading zeroes
func (b Bloom) Big() *big.Int {
return new(big.Int).SetBytes(b[:])
}
// Bytes returns the backing byte slice of the bloom
func (b Bloom) Bytes() []byte {
return b[:]
}
// Test checks if the given topic is present in the bloom filter
func (b Bloom) Test(topic []byte) bool {
i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6))
return v1 == v1&b[i1] &&
v2 == v2&b[i2] &&
v3 == v3&b[i3]
}
// MarshalText encodes b as a hex string with 0x prefix.
func (b Bloom) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}
// UnmarshalText b as a hex string with 0x prefix.
func (b *Bloom) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("Bloom", input, b[:])
}
// CreateBloom creates a bloom filter out of the give Receipts (+Logs)
func CreateBloom(receipts Receipts) Bloom {
buf := make([]byte, 6)
var bin Bloom
for _, receipt := range receipts {
for _, log := range receipt.Logs {
bin.add(log.Address.Bytes(), buf)
for _, b := range log.Topics {
bin.add(b[:], buf)
}
}
}
return bin
}
// LogsBloom returns the bloom bytes for the given logs
func LogsBloom(logs []*Log) []byte {
buf := make([]byte, 6)
var bin Bloom
for _, log := range logs {
bin.add(log.Address.Bytes(), buf)
for _, b := range log.Topics {
bin.add(b[:], buf)
}
}
return bin[:]
}
// Bloom9 returns the bloom filter for the given data
func Bloom9(data []byte) []byte {
var b Bloom
b.SetBytes(data)
return b.Bytes()
}
// bloomValues returns the bytes (index-value pairs) to set for the given data
func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) {
sha := hasherPool.Get().(crypto.KeccakState)
sha.Reset()
sha.Write(data)
sha.Read(hashbuf)
hasherPool.Put(sha)
// The actual bits to flip
v1 := byte(1 << (hashbuf[1] & 0x7))
v2 := byte(1 << (hashbuf[3] & 0x7))
v3 := byte(1 << (hashbuf[5] & 0x7))
// The indices for the bytes to OR in
i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1
i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
return i1, v1, i2, v2, i3, v3
}
// BloomLookup is a convenience-method to check presence in the bloom filter
func BloomLookup(bin Bloom, topic bytesBacked) bool {
return bin.Test(topic.Bytes())
}
-165
View File
@@ -1,165 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func TestBloom(t *testing.T) {
positive := []string{
"testtest",
"test",
"hallo",
"other",
}
negative := []string{
"tes",
"lo",
}
var bloom Bloom
for _, data := range positive {
bloom.Add([]byte(data))
}
for _, data := range positive {
if !bloom.Test([]byte(data)) {
t.Error("expected", data, "to test true")
}
}
for _, data := range negative {
if bloom.Test([]byte(data)) {
t.Error("did not expect", data, "to test true")
}
}
}
// TestBloomExtensively does some more thorough tests
func TestBloomExtensively(t *testing.T) {
var exp = common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263")
var b Bloom
// Add 100 "random" things
for i := 0; i < 100; i++ {
data := fmt.Sprintf("xxxxxxxxxx data %d yyyyyyyyyyyyyy", i)
b.Add([]byte(data))
//b.Add(new(big.Int).SetBytes([]byte(data)))
}
got := crypto.Keccak256Hash(b.Bytes())
if got != exp {
t.Errorf("Got %x, exp %x", got, exp)
}
var b2 Bloom
b2.SetBytes(b.Bytes())
got2 := crypto.Keccak256Hash(b2.Bytes())
if got != got2 {
t.Errorf("Got %x, exp %x", got, got2)
}
}
func BenchmarkBloom9(b *testing.B) {
test := []byte("testestestest")
for i := 0; i < b.N; i++ {
Bloom9(test)
}
}
func BenchmarkBloom9Lookup(b *testing.B) {
toTest := []byte("testtest")
bloom := new(Bloom)
for i := 0; i < b.N; i++ {
bloom.Test(toTest)
}
}
func BenchmarkCreateBloom(b *testing.B) {
var txs = Transactions{
NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil),
NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil),
}
var rSmall = Receipts{
&Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{Address: common.BytesToAddress([]byte{0x11})},
{Address: common.BytesToAddress([]byte{0x01, 0x11})},
},
TxHash: txs[0].Hash(),
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
GasUsed: 1,
},
&Receipt{
PostState: common.Hash{2}.Bytes(),
CumulativeGasUsed: 3,
Logs: []*Log{
{Address: common.BytesToAddress([]byte{0x22})},
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
},
TxHash: txs[1].Hash(),
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
GasUsed: 2,
},
}
var rLarge = make(Receipts, 200)
// Fill it with 200 receipts x 2 logs
for i := 0; i < 200; i += 2 {
copy(rLarge[i:], rSmall)
}
b.Run("small", func(b *testing.B) {
b.ReportAllocs()
var bl Bloom
for i := 0; i < b.N; i++ {
bl = CreateBloom(rSmall)
}
b.StopTimer()
var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
got := crypto.Keccak256Hash(bl.Bytes())
if got != exp {
b.Errorf("Got %x, exp %x", got, exp)
}
})
b.Run("large", func(b *testing.B) {
b.ReportAllocs()
var bl Bloom
for i := 0; i < b.N; i++ {
bl = CreateBloom(rLarge)
}
b.StopTimer()
var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
got := crypto.Keccak256Hash(bl.Bytes())
if got != exp {
b.Errorf("Got %x, exp %x", got, exp)
}
})
}
-43
View File
@@ -1,43 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package types
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common"
)
// MarshalJSON marshals as JSON.
func (a AccessTuple) MarshalJSON() ([]byte, error) {
type AccessTuple struct {
Address common.Address `json:"address" gencodec:"required"`
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"`
}
var enc AccessTuple
enc.Address = a.Address
enc.StorageKeys = a.StorageKeys
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (a *AccessTuple) UnmarshalJSON(input []byte) error {
type AccessTuple struct {
Address *common.Address `json:"address" gencodec:"required"`
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"`
}
var dec AccessTuple
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Address == nil {
return errors.New("missing required field 'address' for AccessTuple")
}
a.Address = *dec.Address
if dec.StorageKeys == nil {
return errors.New("missing required field 'storageKeys' for AccessTuple")
}
a.StorageKeys = dec.StorageKeys
return nil
}
-90
View File
@@ -1,90 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package types
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*logMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (l Log) MarshalJSON() ([]byte, error) {
type Log struct {
Address common.Address `json:"address" gencodec:"required"`
Topics []common.Hash `json:"topics" gencodec:"required"`
Data hexutil.Bytes `json:"data" gencodec:"required"`
BlockNumber hexutil.Uint64 `json:"blockNumber" rlp:"-"`
TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"`
BlockHash common.Hash `json:"blockHash" rlp:"-"`
Index hexutil.Uint `json:"logIndex" rlp:"-"`
Removed bool `json:"removed" rlp:"-"`
}
var enc Log
enc.Address = l.Address
enc.Topics = l.Topics
enc.Data = l.Data
enc.BlockNumber = hexutil.Uint64(l.BlockNumber)
enc.TxHash = l.TxHash
enc.TxIndex = hexutil.Uint(l.TxIndex)
enc.BlockHash = l.BlockHash
enc.Index = hexutil.Uint(l.Index)
enc.Removed = l.Removed
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (l *Log) UnmarshalJSON(input []byte) error {
type Log struct {
Address *common.Address `json:"address" gencodec:"required"`
Topics []common.Hash `json:"topics" gencodec:"required"`
Data *hexutil.Bytes `json:"data" gencodec:"required"`
BlockNumber *hexutil.Uint64 `json:"blockNumber" rlp:"-"`
TxHash *common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"`
BlockHash *common.Hash `json:"blockHash" rlp:"-"`
Index *hexutil.Uint `json:"logIndex" rlp:"-"`
Removed *bool `json:"removed" rlp:"-"`
}
var dec Log
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Address == nil {
return errors.New("missing required field 'address' for Log")
}
l.Address = *dec.Address
if dec.Topics == nil {
return errors.New("missing required field 'topics' for Log")
}
l.Topics = dec.Topics
if dec.Data == nil {
return errors.New("missing required field 'data' for Log")
}
l.Data = *dec.Data
if dec.BlockNumber != nil {
l.BlockNumber = uint64(*dec.BlockNumber)
}
if dec.TxHash == nil {
return errors.New("missing required field 'transactionHash' for Log")
}
l.TxHash = *dec.TxHash
if dec.TxIndex != nil {
l.TxIndex = uint(*dec.TxIndex)
}
if dec.BlockHash != nil {
l.BlockHash = *dec.BlockHash
}
if dec.Index != nil {
l.Index = uint(*dec.Index)
}
if dec.Removed != nil {
l.Removed = *dec.Removed
}
return nil
}
-23
View File
@@ -1,23 +0,0 @@
// Code generated by rlpgen. DO NOT EDIT.
package types
import (
"io"
"github.com/ethereum/go-ethereum/rlp"
)
func (obj *Log) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteBytes(obj.Address[:])
_tmp1 := w.List()
for _, _tmp2 := range obj.Topics {
w.WriteBytes(_tmp2[:])
}
w.ListEnd(_tmp1)
w.WriteBytes(obj.Data)
w.ListEnd(_tmp0)
return w.Flush()
}
-128
View File
@@ -1,128 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package types
import (
"encoding/json"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*receiptMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (r Receipt) MarshalJSON() ([]byte, error) {
type Receipt struct {
Type hexutil.Uint64 `json:"type,omitempty"`
PostState hexutil.Bytes `json:"root"`
Status hexutil.Uint64 `json:"status"`
CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"`
TxHash common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress common.Address `json:"contractAddress"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"`
BlobGasUsed hexutil.Uint64 `json:"blobGasUsed,omitempty"`
BlobGasPrice *hexutil.Big `json:"blobGasPrice,omitempty"`
BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex hexutil.Uint `json:"transactionIndex"`
}
var enc Receipt
enc.Type = hexutil.Uint64(r.Type)
enc.PostState = r.PostState
enc.Status = hexutil.Uint64(r.Status)
enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed)
enc.Bloom = r.Bloom
enc.Logs = r.Logs
enc.TxHash = r.TxHash
enc.ContractAddress = r.ContractAddress
enc.GasUsed = hexutil.Uint64(r.GasUsed)
enc.EffectiveGasPrice = (*hexutil.Big)(r.EffectiveGasPrice)
enc.BlobGasUsed = hexutil.Uint64(r.BlobGasUsed)
enc.BlobGasPrice = (*hexutil.Big)(r.BlobGasPrice)
enc.BlockHash = r.BlockHash
enc.BlockNumber = (*hexutil.Big)(r.BlockNumber)
enc.TransactionIndex = hexutil.Uint(r.TransactionIndex)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (r *Receipt) UnmarshalJSON(input []byte) error {
type Receipt struct {
Type *hexutil.Uint64 `json:"type,omitempty"`
PostState *hexutil.Bytes `json:"root"`
Status *hexutil.Uint64 `json:"status"`
CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"`
TxHash *common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress *common.Address `json:"contractAddress"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed,omitempty"`
BlobGasPrice *hexutil.Big `json:"blobGasPrice,omitempty"`
BlockHash *common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex *hexutil.Uint `json:"transactionIndex"`
}
var dec Receipt
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Type != nil {
r.Type = uint8(*dec.Type)
}
if dec.PostState != nil {
r.PostState = *dec.PostState
}
if dec.Status != nil {
r.Status = uint64(*dec.Status)
}
if dec.CumulativeGasUsed == nil {
return errors.New("missing required field 'cumulativeGasUsed' for Receipt")
}
r.CumulativeGasUsed = uint64(*dec.CumulativeGasUsed)
if dec.Bloom == nil {
return errors.New("missing required field 'logsBloom' for Receipt")
}
r.Bloom = *dec.Bloom
if dec.Logs == nil {
return errors.New("missing required field 'logs' for Receipt")
}
r.Logs = dec.Logs
if dec.TxHash == nil {
return errors.New("missing required field 'transactionHash' for Receipt")
}
r.TxHash = *dec.TxHash
if dec.ContractAddress != nil {
r.ContractAddress = *dec.ContractAddress
}
if dec.GasUsed == nil {
return errors.New("missing required field 'gasUsed' for Receipt")
}
r.GasUsed = uint64(*dec.GasUsed)
if dec.EffectiveGasPrice != nil {
r.EffectiveGasPrice = (*big.Int)(dec.EffectiveGasPrice)
}
if dec.BlobGasUsed != nil {
r.BlobGasUsed = uint64(*dec.BlobGasUsed)
}
if dec.BlobGasPrice != nil {
r.BlobGasPrice = (*big.Int)(dec.BlobGasPrice)
}
if dec.BlockHash != nil {
r.BlockHash = *dec.BlockHash
}
if dec.BlockNumber != nil {
r.BlockNumber = (*big.Int)(dec.BlockNumber)
}
if dec.TransactionIndex != nil {
r.TransactionIndex = uint(*dec.TransactionIndex)
}
return nil
}
-30
View File
@@ -28,8 +28,6 @@ package types
import (
"bytes"
"fmt"
"math"
"sync"
"github.com/ethereum/go-ethereum/common"
@@ -48,22 +46,6 @@ var encodeBufferPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
// getPooledBuffer retrieves a buffer from the pool and creates a byte slice of the
// requested size from it.
//
// The caller should return the *bytes.Buffer object back into encodeBufferPool after use!
// The returned byte slice must not be used after returning the buffer.
func getPooledBuffer(size uint64) ([]byte, *bytes.Buffer, error) {
if size > math.MaxInt {
return nil, nil, fmt.Errorf("can't get buffer of size %d", size)
}
buf := encodeBufferPool.Get().(*bytes.Buffer)
buf.Reset()
buf.Grow(int(size))
b := buf.Bytes()[:int(size)]
return b, buf, nil
}
// rlpHash encodes x and hashes the encoded bytes.
func rlpHash(x interface{}) (h common.Hash) {
sha := hasherPool.Get().(crypto.KeccakState)
@@ -74,18 +56,6 @@ func rlpHash(x interface{}) (h common.Hash) {
return h
}
// prefixedRlpHash writes the prefix into the hasher before rlp-encoding x.
// It's used for typed transactions.
func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) {
sha := hasherPool.Get().(crypto.KeccakState)
defer hasherPool.Put(sha)
sha.Reset()
sha.Write([]byte{prefix})
rlp.Encode(sha, x)
sha.Read(h[:])
return h
}
// TrieHasher is the tool used to calculate the hash of derivable list.
// This is internal, do not use.
type TrieHasher interface {
+77
View File
@@ -0,0 +1,77 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package types
import (
gethtypes "github.com/ethereum/go-ethereum/core/types"
)
// The following types are used directly as their upstream definitions.
// So we list them all here to avoid having many individual files.
type (
AccessList = gethtypes.AccessList
AccessTuple = gethtypes.AccessTuple
AccessListTx = gethtypes.AccessListTx
Bloom = gethtypes.Bloom
Receipt = gethtypes.Receipt
Receipts = gethtypes.Receipts
ReceiptForStorage = gethtypes.ReceiptForStorage
LegacyTx = gethtypes.LegacyTx
DynamicFeeTx = gethtypes.DynamicFeeTx
BlobTx = gethtypes.BlobTx
BlobTxSidecar = gethtypes.BlobTxSidecar
Signer = gethtypes.Signer
HomesteadSigner = gethtypes.HomesteadSigner
FrontierSigner = gethtypes.FrontierSigner
Transaction = gethtypes.Transaction
Transactions = gethtypes.Transactions
TxByNonce = gethtypes.TxByNonce
TxData = gethtypes.TxData
)
// The following constants are used directly as their upstream definitions.
const (
BloomBitLength = gethtypes.BloomBitLength
BloomByteLength = gethtypes.BloomByteLength
ReceiptStatusFailed = gethtypes.ReceiptStatusFailed
ReceiptStatusSuccessful = gethtypes.ReceiptStatusSuccessful
// Transaction types.
LegacyTxType = gethtypes.LegacyTxType
AccessListTxType = gethtypes.AccessListTxType
DynamicFeeTxType = gethtypes.DynamicFeeTxType
BlobTxType = gethtypes.BlobTxType
)
// The following functions are used directly as their upstream definitions.
var (
BloomLookup = gethtypes.BloomLookup
BytesToBloom = gethtypes.BytesToBloom
CreateBloom = gethtypes.CreateBloom
NewReceipt = gethtypes.NewReceipt
NewContractCreation = gethtypes.NewContractCreation
NewTransaction = gethtypes.NewTransaction
// Signers
NewEIP155Signer = gethtypes.NewEIP155Signer
NewEIP2930Signer = gethtypes.NewEIP2930Signer
NewLondonSigner = gethtypes.NewLondonSigner
NewCancunSigner = gethtypes.NewCancunSigner
MakeSigner = gethtypes.MakeSigner
LatestSigner = gethtypes.LatestSigner
LatestSignerForChainID = gethtypes.LatestSignerForChainID
SignTx = gethtypes.SignTx
SignNewTx = gethtypes.SignNewTx
MustSignNewTx = gethtypes.MustSignNewTx
Sender = gethtypes.Sender
// Transactions
NewTx = gethtypes.NewTx
TxDifference = gethtypes.TxDifference
// Errors
ErrTxTypeNotSupported = gethtypes.ErrTxTypeNotSupported
ErrGasFeeCapTooLow = gethtypes.ErrGasFeeCapTooLow
)
+2 -40
View File
@@ -26,49 +26,11 @@
package types
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -type Log -out gen_log_rlp.go
//go:generate go run github.com/fjl/gencodec -type Log -field-override logMarshaling -out gen_log_json.go
import gethtypes "github.com/ethereum/go-ethereum/core/types"
// Log represents a contract log event. These events are generated by the LOG opcode and
// stored/indexed by the node.
type Log struct {
// Consensus fields:
// address of the contract that generated the event
Address common.Address `json:"address" gencodec:"required"`
// list of topics provided by the contract.
Topics []common.Hash `json:"topics" gencodec:"required"`
// supplied by the contract, usually ABI-encoded
Data []byte `json:"data" gencodec:"required"`
// Derived fields. These fields are filled in by the node
// but not secured by consensus.
// block in which the transaction was included
BlockNumber uint64 `json:"blockNumber" rlp:"-"`
// hash of the transaction
TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
// index of the transaction in the block
TxIndex uint `json:"transactionIndex" rlp:"-"`
// hash of the block in which the transaction was included
BlockHash common.Hash `json:"blockHash" rlp:"-"`
// index of the log in the block
Index uint `json:"logIndex" rlp:"-"`
// The Removed field is true if this log was reverted due to a chain reorganisation.
// You must pay attention to this field if you receive logs through a filter query.
Removed bool `json:"removed" rlp:"-"`
}
type logMarshaling struct {
Data hexutil.Bytes
BlockNumber hexutil.Uint64
TxIndex hexutil.Uint
Index hexutil.Uint
}
type Log = gethtypes.Log
// FlattenLogs converts a nested array of logs to a single array of logs.
func FlattenLogs(list [][]*Log) []*Log {

Some files were not shown because too many files have changed in this diff Show More