mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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:
committed by
GitHub
co-authored by
Arran Schlosberg
parent
7ce72518dd
commit
e3a01fb149
@@ -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"
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Vendored
-280
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)]
|
||||
}
|
||||
Vendored
-1
@@ -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}
|
||||
Vendored
-1
@@ -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}
|
||||
Vendored
-1
@@ -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 +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
@@ -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
@@ -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
@@ -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
@@ -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}
|
||||
BIN
Binary file not shown.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 +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}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}},
|
||||
}
|
||||
|
||||
@@ -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
@@ -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: ¶ms.ChainConfig{HomesteadBlock: new(big.Int), FeeConfig: params.DefaultFeeConfig},
|
||||
Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}},
|
||||
Config: params.WithExtra(
|
||||
¶ms.ChainConfig{HomesteadBlock: new(big.Int)},
|
||||
¶ms.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: ¶ms.ChainConfig{HomesteadBlock: new(big.Int), FeeConfig: params.DefaultFeeConfig},
|
||||
Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}},
|
||||
Config: params.WithExtra(
|
||||
¶ms.ChainConfig{HomesteadBlock: new(big.Int)},
|
||||
¶ms.ChainConfigExtra{FeeConfig: params.DefaultFeeConfig},
|
||||
),
|
||||
Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}},
|
||||
}
|
||||
|
||||
blockchain, err := create(chainDB, gspec, common.Hash{})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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, ¶ms.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
@@ -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))
|
||||
|
||||
@@ -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
@@ -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 }
|
||||
|
||||
@@ -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: ¶ms.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(
|
||||
¶ms.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),
|
||||
},
|
||||
¶ms.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 = ¶ms.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(
|
||||
¶ms.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),
|
||||
¶ms.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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user