mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
squashed all from geth-v1.12.2-x (#1117)
* squashed all from geth-v1.12.2-x * fix merge
This commit is contained in:
@@ -39,7 +39,7 @@ import (
|
||||
var (
|
||||
// ErrNoCode is returned by call and transact operations for which the requested
|
||||
// recipient contract to operate on does not exist in the state db or does not
|
||||
// have any code associated with it (i.e. suicided).
|
||||
// have any code associated with it (i.e. self-destructed).
|
||||
ErrNoCode = errors.New("no contract code at given address")
|
||||
|
||||
// ErrNoAcceptedState is raised when attempting to perform a accepted state action
|
||||
|
||||
@@ -62,8 +62,6 @@ import (
|
||||
var (
|
||||
_ bind.AcceptedContractCaller = (*SimulatedBackend)(nil)
|
||||
_ bind.ContractBackend = (*SimulatedBackend)(nil)
|
||||
_ bind.ContractFilterer = (*SimulatedBackend)(nil)
|
||||
_ bind.ContractTransactor = (*SimulatedBackend)(nil)
|
||||
_ bind.DeployBackend = (*SimulatedBackend)(nil)
|
||||
|
||||
_ interfaces.ChainReader = (*SimulatedBackend)(nil)
|
||||
@@ -147,7 +145,7 @@ func (b *SimulatedBackend) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit imports all the pending transactions as a single block and starts a
|
||||
// Commit imports all the accepted transactions as a single block and starts a
|
||||
// fresh new state.
|
||||
func (b *SimulatedBackend) Commit(accept bool) common.Hash {
|
||||
b.mu.Lock()
|
||||
@@ -171,7 +169,7 @@ func (b *SimulatedBackend) Commit(accept bool) common.Hash {
|
||||
return blockHash
|
||||
}
|
||||
|
||||
// Rollback aborts all pending transactions, reverting to the last committed state.
|
||||
// Rollback aborts all accepted transactions, reverting to the last committed state.
|
||||
func (b *SimulatedBackend) Rollback() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
@@ -206,7 +204,7 @@ func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error {
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if len(b.acceptedBlock.Transactions()) != 0 {
|
||||
return errors.New("pending block dirty")
|
||||
return errors.New("accepted block dirty")
|
||||
}
|
||||
block, err := b.blockByHash(ctx, parent)
|
||||
if err != nil {
|
||||
@@ -293,10 +291,10 @@ func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
// TransactionByHash checks the pool of pending transactions in addition to the
|
||||
// blockchain. The isPending return value indicates whether the transaction has been
|
||||
// TransactionByHash checks the pool of accepted transactions in addition to the
|
||||
// blockchain. The isAccepted return value indicates whether the transaction has been
|
||||
// mined yet. Note that the transaction may not be part of the canonical chain even if
|
||||
// it's not pending.
|
||||
// it's not accepted.
|
||||
func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
@@ -537,7 +535,7 @@ func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, erro
|
||||
return big.NewInt(1), nil
|
||||
}
|
||||
|
||||
// EstimateGas executes the requested code against the currently pending block/state and
|
||||
// EstimateGas executes the requested code against the currently accepted block/state and
|
||||
// returns the used amount of gas.
|
||||
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call interfaces.CallMsg) (uint64, error) {
|
||||
b.mu.Lock()
|
||||
@@ -641,7 +639,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call interfaces.Call
|
||||
return hi, nil
|
||||
}
|
||||
|
||||
// callContract implements common code between normal and pending contract calls.
|
||||
// callContract implements common code between normal and accepted contract calls.
|
||||
// state is modified during execution, make sure to copy it if necessary.
|
||||
func (b *SimulatedBackend) callContract(ctx context.Context, call interfaces.CallMsg, header *types.Header, stateDB *state.StateDB) (*core.ExecutionResult, error) {
|
||||
// Gas prices post 1559 need to be initialized
|
||||
@@ -711,7 +709,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call interfaces.Cal
|
||||
return core.ApplyMessage(vmEnv, msg, gasPool)
|
||||
}
|
||||
|
||||
// SendTransaction updates the pending block to include the given transaction.
|
||||
// SendTransaction updates the accepted block to include the given transaction.
|
||||
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
@@ -854,7 +852,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||
}
|
||||
block := b.blockchain.GetBlockByHash(b.acceptedBlock.ParentHash())
|
||||
if block == nil {
|
||||
return fmt.Errorf("could not find parent")
|
||||
return errors.New("could not find parent")
|
||||
}
|
||||
|
||||
blocks, _, _ := core.GenerateChain(b.config, block, dummy.NewFaker(), b.database, 1, 10, func(number int, block *core.BlockGen) {
|
||||
|
||||
@@ -163,6 +163,7 @@ func TestAdjustTime(t *testing.T) {
|
||||
func TestNewAdjustTimeFail(t *testing.T) {
|
||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
sim := simTestBackend(testAddr)
|
||||
defer sim.blockchain.Stop()
|
||||
|
||||
// Create tx and send
|
||||
head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||
|
||||
@@ -151,7 +151,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s
|
||||
normalized := original
|
||||
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||
// Ensure there is no duplicated identifier
|
||||
identifiers := callIdentifiers
|
||||
var identifiers = callIdentifiers
|
||||
if !original.IsConstant() {
|
||||
identifiers = transactIdentifiers
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ type Error struct {
|
||||
str string
|
||||
|
||||
// Sig contains the string signature according to the ABI spec.
|
||||
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||
// Please note that "int" is substitute for its canonical representation "int256"
|
||||
Sig string
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ type Event struct {
|
||||
// e.g. event foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||
// Please note that "int" is substitute for its canonical representation "int256"
|
||||
Sig string
|
||||
|
||||
// ID returns the canonical representation of the event's signature used by the
|
||||
// abi definition to identify event names and types.
|
||||
ID common.Hash
|
||||
|
||||
@@ -238,7 +238,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri
|
||||
structFieldName := ToCamelCase(argName)
|
||||
|
||||
if structFieldName == "" {
|
||||
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
||||
return nil, errors.New("abi: purely underscored output cannot unpack to struct")
|
||||
}
|
||||
|
||||
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
||||
|
||||
@@ -80,7 +80,7 @@ var (
|
||||
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||
// check that array brackets are equal if they exist
|
||||
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||
return Type{}, fmt.Errorf("invalid arg type in abi")
|
||||
return Type{}, errors.New("invalid arg type in abi")
|
||||
}
|
||||
typ.stringKind = t
|
||||
|
||||
@@ -119,7 +119,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
|
||||
}
|
||||
typ.stringKind = embeddedType.stringKind + sliced
|
||||
} else {
|
||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||
return Type{}, errors.New("invalid formatting of array type")
|
||||
}
|
||||
return typ, err
|
||||
}
|
||||
@@ -356,7 +356,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// requireLengthPrefix returns whether the type requires any sort of length
|
||||
// requiresLengthPrefix returns whether the type requires any sort of length
|
||||
// prefixing.
|
||||
func (t Type) requiresLengthPrefix() bool {
|
||||
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||
|
||||
@@ -28,6 +28,7 @@ package abi
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
@@ -135,7 +136,7 @@ func readBool(word []byte) (bool, error) {
|
||||
// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
||||
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||
if t.T != FunctionTy {
|
||||
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
|
||||
return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array")
|
||||
}
|
||||
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
||||
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
||||
@@ -148,7 +149,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||
// ReadFixedBytes uses reflection to create a fixed array to be read from.
|
||||
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||
if t.T != FixedBytesTy {
|
||||
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
|
||||
return nil, errors.New("abi: invalid type in call to make fixed byte array")
|
||||
}
|
||||
// convert
|
||||
array := reflect.New(t.GetType()).Elem()
|
||||
@@ -176,7 +177,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
|
||||
// declare our array
|
||||
refSlice = reflect.New(t.GetType()).Elem()
|
||||
} else {
|
||||
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
|
||||
return nil, errors.New("abi: invalid type in array/slice unpacking stage")
|
||||
}
|
||||
|
||||
// Arrays have packed elements, resulting in longer unpack steps.
|
||||
|
||||
Vendored
+7
-6
@@ -27,6 +27,7 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
@@ -108,11 +109,11 @@ func (api *ExternalSigner) Status() (string, error) {
|
||||
}
|
||||
|
||||
func (api *ExternalSigner) Open(passphrase string) error {
|
||||
return fmt.Errorf("operation not supported on external signers")
|
||||
return errors.New("operation not supported on external signers")
|
||||
}
|
||||
|
||||
func (api *ExternalSigner) Close() error {
|
||||
return fmt.Errorf("operation not supported on external signers")
|
||||
return errors.New("operation not supported on external signers")
|
||||
}
|
||||
|
||||
func (api *ExternalSigner) Accounts() []accounts.Account {
|
||||
@@ -155,7 +156,7 @@ func (api *ExternalSigner) Contains(account accounts.Account) bool {
|
||||
}
|
||||
|
||||
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||
return accounts.Account{}, fmt.Errorf("operation not supported on external signers")
|
||||
return accounts.Account{}, errors.New("operation not supported on external signers")
|
||||
}
|
||||
|
||||
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain interfaces.ChainStateReader) {
|
||||
@@ -252,14 +253,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
||||
}
|
||||
|
||||
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||
return []byte{}, fmt.Errorf("password-operations not supported on external signers")
|
||||
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, fmt.Errorf("password-operations not supported on external signers")
|
||||
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, fmt.Errorf("password-operations not supported on external signers")
|
||||
return nil, errors.New("password-operations not supported on external signers")
|
||||
}
|
||||
|
||||
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
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
|
||||
@@ -48,11 +49,10 @@ import (
|
||||
// exist yet, the code will attempt to create a watcher at most this often.
|
||||
const minReloadInterval = 2 * time.Second
|
||||
|
||||
type accountsByURL []accounts.Account
|
||||
|
||||
func (s accountsByURL) Len() int { return len(s) }
|
||||
func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
|
||||
func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
// 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.
|
||||
@@ -77,7 +77,7 @@ type accountCache struct {
|
||||
keydir string
|
||||
watcher *watcher
|
||||
mu sync.Mutex
|
||||
all accountsByURL
|
||||
all []accounts.Account
|
||||
byAddr map[common.Address][]accounts.Account
|
||||
throttle *time.Timer
|
||||
notify chan struct{}
|
||||
@@ -204,7 +204,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
|
||||
default:
|
||||
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
||||
copy(err.Matches, matches)
|
||||
sort.Sort(accountsByURL(err.Matches))
|
||||
slices.SortFunc(err.Matches, byURL)
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/cespare/cp"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -84,7 +85,7 @@ func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||
select {
|
||||
case <-ks.changes:
|
||||
default:
|
||||
return fmt.Errorf("wasn't notified of new accounts")
|
||||
return errors.New("wasn't notified of new accounts")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -212,7 +213,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
|
||||
// Check that the account list is sorted by filename.
|
||||
wantAccounts := make([]accounts.Account, len(accs))
|
||||
copy(wantAccounts, accs)
|
||||
sort.Sort(accountsByURL(wantAccounts))
|
||||
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))
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -41,6 +40,7 @@ import (
|
||||
"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)
|
||||
@@ -410,19 +410,19 @@ func TestImportRace(t *testing.T) {
|
||||
t.Fatalf("failed to export account: %v", acc)
|
||||
}
|
||||
_, ks2 := tmpKeyStore(t, true)
|
||||
var atom uint32
|
||||
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 {
|
||||
atomic.AddUint32(&atom, 1)
|
||||
atom.Add(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if atom != 1 {
|
||||
if atom.Load() != 1 {
|
||||
t.Errorf("Import is racy")
|
||||
}
|
||||
}
|
||||
@@ -437,7 +437,7 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle
|
||||
for _, account := range live {
|
||||
liveList = append(liveList, account)
|
||||
}
|
||||
sort.Sort(accountsByURL(liveList))
|
||||
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))
|
||||
|
||||
@@ -235,10 +235,13 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := crypto.ToECDSAUnsafe(keyBytes)
|
||||
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, err
|
||||
return nil, fmt.Errorf("invalid UUID: %w", err)
|
||||
}
|
||||
return &Key{
|
||||
Id: id,
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@@ -135,7 +136,7 @@ func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
|
||||
// Unpair disestablishes an existing pairing.
|
||||
func (s *SecureChannelSession) Unpair() error {
|
||||
if s.PairingKey == nil {
|
||||
return fmt.Errorf("cannot unpair: not paired")
|
||||
return errors.New("cannot unpair: not paired")
|
||||
}
|
||||
|
||||
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
|
||||
@@ -151,7 +152,7 @@ func (s *SecureChannelSession) Unpair() error {
|
||||
// Open initializes the secure channel.
|
||||
func (s *SecureChannelSession) Open() error {
|
||||
if s.iv != nil {
|
||||
return fmt.Errorf("session already opened")
|
||||
return errors.New("session already opened")
|
||||
}
|
||||
|
||||
response, err := s.open()
|
||||
@@ -225,7 +226,7 @@ func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error
|
||||
// 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, fmt.Errorf("channel not open")
|
||||
return nil, errors.New("channel not open")
|
||||
}
|
||||
|
||||
data, err := s.encryptAPDU(data)
|
||||
@@ -264,7 +265,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b
|
||||
return nil, err
|
||||
}
|
||||
if !bytes.Equal(s.iv, rmac) {
|
||||
return nil, fmt.Errorf("invalid MAC in response")
|
||||
return nil, errors.New("invalid MAC in response")
|
||||
}
|
||||
|
||||
rapdu := &responseAPDU{}
|
||||
@@ -329,7 +330,7 @@ func unpad(data []byte, terminator byte) ([]byte, error) {
|
||||
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("expected end of padding, got 0")
|
||||
return nil, errors.New("expected end of padding, got 0")
|
||||
}
|
||||
|
||||
// updateIV is an internal method that updates the initialization vector after
|
||||
|
||||
@@ -262,7 +262,7 @@ func (w *Wallet) release() error {
|
||||
// with the wallet.
|
||||
func (w *Wallet) pair(puk []byte) error {
|
||||
if w.session.paired() {
|
||||
return fmt.Errorf("wallet already paired")
|
||||
return errors.New("wallet already paired")
|
||||
}
|
||||
pairing, err := w.session.pair(puk)
|
||||
if err != nil {
|
||||
@@ -823,7 +823,7 @@ func (s *Session) pair(secret []byte) (smartcardPairing, error) {
|
||||
// unpair deletes an existing pairing.
|
||||
func (s *Session) unpair() error {
|
||||
if !s.verified {
|
||||
return fmt.Errorf("unpair requires that the PIN be verified")
|
||||
return errors.New("unpair requires that the PIN be verified")
|
||||
}
|
||||
return s.Channel.Unpair()
|
||||
}
|
||||
@@ -917,7 +917,7 @@ func (s *Session) initialize(seed []byte) error {
|
||||
return err
|
||||
}
|
||||
if status == "Online" {
|
||||
return fmt.Errorf("card is already initialized, cowardly refusing to proceed")
|
||||
return errors.New("card is already initialized, cowardly refusing to proceed")
|
||||
}
|
||||
|
||||
s.Wallet.lock.Lock()
|
||||
|
||||
+1
-1
@@ -342,7 +342,7 @@ To make `t8n` apply these, the following inputs are required:
|
||||
- For ethash, it is `5000000000000000000` `wei`,
|
||||
- If this is not defined, mining rewards are not applied,
|
||||
- A value of `0` is valid, and causes accounts to be 'touched'.
|
||||
- For each ommer, the tool needs to be given an `addres\` and a `delta`. This
|
||||
- For each ommer, the tool needs to be given an `address\` and a `delta`. This
|
||||
is done via the `ommers` field in `env`.
|
||||
|
||||
Note: the tool does not verify that e.g. the normal uncle rules apply,
|
||||
|
||||
@@ -271,7 +271,7 @@ func readInput(ctx *cli.Context) (*bbInput, error) {
|
||||
return inputData, nil
|
||||
}
|
||||
|
||||
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||
// dispatchBlock writes the output data to either stderr or stdout, or to the specified
|
||||
// files
|
||||
func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error {
|
||||
raw, _ := rlp.EncodeToBytes(block)
|
||||
|
||||
@@ -29,8 +29,8 @@ package t8ntool
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus/misc/eip4844"
|
||||
"github.com/ava-labs/subnet-evm/core"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/state"
|
||||
@@ -55,16 +55,18 @@ type Prestate struct {
|
||||
// ExecutionResult contains the execution status after running a state test, any
|
||||
// error that might have occurred and a dump of the final state if requested.
|
||||
type ExecutionResult struct {
|
||||
StateRoot common.Hash `json:"stateRoot"`
|
||||
TxRoot common.Hash `json:"txRoot"`
|
||||
ReceiptRoot common.Hash `json:"receiptsRoot"`
|
||||
LogsHash common.Hash `json:"logsHash"`
|
||||
Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Receipts types.Receipts `json:"receipts"`
|
||||
Rejected []*rejectedTx `json:"rejected,omitempty"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
|
||||
StateRoot common.Hash `json:"stateRoot"`
|
||||
TxRoot common.Hash `json:"txRoot"`
|
||||
ReceiptRoot common.Hash `json:"receiptsRoot"`
|
||||
LogsHash common.Hash `json:"logsHash"`
|
||||
Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Receipts types.Receipts `json:"receipts"`
|
||||
Rejected []*rejectedTx `json:"rejected,omitempty"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
|
||||
CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
|
||||
CurrentBlobGasUsed *math.HexOrDecimal64 `json:"currentBlobGasUsed,omitempty"`
|
||||
}
|
||||
|
||||
type ommer struct {
|
||||
@@ -74,38 +76,44 @@ type ommer struct {
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go
|
||||
type stEnv struct {
|
||||
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
|
||||
Difficulty *big.Int `json:"currentDifficulty"`
|
||||
Random *big.Int `json:"currentRandom"`
|
||||
ParentDifficulty *big.Int `json:"parentDifficulty"`
|
||||
ParentBaseFee *big.Int `json:"parentBaseFee,omitempty"`
|
||||
ParentGasUsed uint64 `json:"parentGasUsed,omitempty"`
|
||||
ParentGasLimit uint64 `json:"parentGasLimit,omitempty"`
|
||||
MinBaseFee *big.Int `json:"minBaseFee,omitempty"`
|
||||
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
|
||||
Number uint64 `json:"currentNumber" gencodec:"required"`
|
||||
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
|
||||
ParentTimestamp uint64 `json:"parentTimestamp,omitempty"`
|
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||
Ommers []ommer `json:"ommers,omitempty"`
|
||||
BaseFee *big.Int `json:"currentBaseFee,omitempty"`
|
||||
ParentUncleHash common.Hash `json:"parentUncleHash"`
|
||||
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
|
||||
Difficulty *big.Int `json:"currentDifficulty"`
|
||||
Random *big.Int `json:"currentRandom"`
|
||||
ParentDifficulty *big.Int `json:"parentDifficulty"`
|
||||
ParentBaseFee *big.Int `json:"parentBaseFee,omitempty"`
|
||||
ParentGasUsed uint64 `json:"parentGasUsed,omitempty"`
|
||||
ParentGasLimit uint64 `json:"parentGasLimit,omitempty"`
|
||||
MinBaseFee *big.Int `json:"minBaseFee,omitempty"`
|
||||
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
|
||||
Number uint64 `json:"currentNumber" gencodec:"required"`
|
||||
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
|
||||
ParentTimestamp uint64 `json:"parentTimestamp,omitempty"`
|
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||
Ommers []ommer `json:"ommers,omitempty"`
|
||||
BaseFee *big.Int `json:"currentBaseFee,omitempty"`
|
||||
ParentUncleHash common.Hash `json:"parentUncleHash"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas,omitempty"`
|
||||
ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"`
|
||||
ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"`
|
||||
}
|
||||
|
||||
type stEnvMarshaling struct {
|
||||
Coinbase common.UnprefixedAddress
|
||||
Difficulty *math.HexOrDecimal256
|
||||
Random *math.HexOrDecimal256
|
||||
ParentDifficulty *math.HexOrDecimal256
|
||||
ParentBaseFee *math.HexOrDecimal256
|
||||
ParentGasUsed math.HexOrDecimal64
|
||||
ParentGasLimit math.HexOrDecimal64
|
||||
MinBaseFee *math.HexOrDecimal256
|
||||
GasLimit math.HexOrDecimal64
|
||||
Number math.HexOrDecimal64
|
||||
Timestamp math.HexOrDecimal64
|
||||
ParentTimestamp math.HexOrDecimal64
|
||||
BaseFee *math.HexOrDecimal256
|
||||
Coinbase common.UnprefixedAddress
|
||||
Difficulty *math.HexOrDecimal256
|
||||
Random *math.HexOrDecimal256
|
||||
ParentDifficulty *math.HexOrDecimal256
|
||||
ParentBaseFee *math.HexOrDecimal256
|
||||
ParentGasUsed math.HexOrDecimal64
|
||||
ParentGasLimit math.HexOrDecimal64
|
||||
MinBaseFee *math.HexOrDecimal256
|
||||
GasLimit math.HexOrDecimal64
|
||||
Number math.HexOrDecimal64
|
||||
Timestamp math.HexOrDecimal64
|
||||
ParentTimestamp math.HexOrDecimal64
|
||||
BaseFee *math.HexOrDecimal256
|
||||
ExcessBlobGas *math.HexOrDecimal64
|
||||
ParentExcessBlobGas *math.HexOrDecimal64
|
||||
ParentBlobGasUsed *math.HexOrDecimal64
|
||||
}
|
||||
|
||||
type rejectedTx struct {
|
||||
@@ -163,6 +171,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
// rnd := common.BigToHash(pre.Env.Random)
|
||||
// vmContext.Random = &rnd
|
||||
// }
|
||||
// If excessBlobGas is defined, add it to the vmContext.
|
||||
if pre.Env.ExcessBlobGas != nil {
|
||||
vmContext.ExcessBlobGas = pre.Env.ExcessBlobGas
|
||||
} else {
|
||||
// If it is not explicitly defined, but we have the parent values, we try
|
||||
// to calculate it ourselves.
|
||||
parentExcessBlobGas := pre.Env.ParentExcessBlobGas
|
||||
parentBlobGasUsed := pre.Env.ParentBlobGasUsed
|
||||
if parentExcessBlobGas != nil && parentBlobGasUsed != nil {
|
||||
excessBlobGas := eip4844.CalcExcessBlobGas(*parentExcessBlobGas, *parentBlobGasUsed)
|
||||
vmContext.ExcessBlobGas = &excessBlobGas
|
||||
}
|
||||
}
|
||||
// If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's
|
||||
// done in StateProcessor.Process(block, ...), right before transactions are applied.
|
||||
// if chainConfig.DAOForkSupport &&
|
||||
@@ -170,8 +191,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
// chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 {
|
||||
// misc.ApplyDAOHardFork(statedb)
|
||||
// }
|
||||
|
||||
var blobGasUsed uint64
|
||||
for i, tx := range txs {
|
||||
if tx.Type() == types.BlobTxType && vmContext.ExcessBlobGas == nil {
|
||||
errMsg := "blob tx used but field env.ExcessBlobGas missing"
|
||||
log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", errMsg)
|
||||
rejectedTxs = append(rejectedTxs, &rejectedTx{i, errMsg})
|
||||
continue
|
||||
}
|
||||
msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee)
|
||||
if err != nil {
|
||||
log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err)
|
||||
@@ -201,6 +228,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
gaspool.SetGas(prevGas)
|
||||
continue
|
||||
}
|
||||
if tx.Type() == types.BlobTxType {
|
||||
blobGasUsed += params.BlobTxBlobGasPerBlob
|
||||
}
|
||||
includedTxs = append(includedTxs, tx)
|
||||
if hashError != nil {
|
||||
return nil, nil, NewError(ErrorMissingBlockhash, hashError)
|
||||
@@ -249,7 +279,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
if miningReward >= 0 {
|
||||
// Add mining reward. The mining reward may be `0`, which only makes a difference in the cases
|
||||
// where
|
||||
// - the coinbase suicided, or
|
||||
// - the coinbase self-destructed, or
|
||||
// - there are only 'bad' transactions, which aren't executed. In those cases,
|
||||
// the coinbase gets no txfee, so isn't created, and thus needs to be touched
|
||||
var (
|
||||
@@ -270,9 +300,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
statedb.AddBalance(pre.Env.Coinbase, minerReward)
|
||||
}
|
||||
// Commit block
|
||||
root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber), false)
|
||||
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), false)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not commit state: %v", err)
|
||||
return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
|
||||
}
|
||||
execRs := &ExecutionResult{
|
||||
@@ -287,6 +316,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
GasUsed: (math.HexOrDecimal64)(gasUsed),
|
||||
BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee),
|
||||
}
|
||||
if vmContext.ExcessBlobGas != nil {
|
||||
execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(vmContext.ExcessBlobGas)
|
||||
execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed)
|
||||
}
|
||||
// Re-create statedb instance with new root upon the updated database
|
||||
// for accessing latest states.
|
||||
statedb, err = state.New(root, statedb.Database(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err))
|
||||
}
|
||||
return statedb, execRs, nil
|
||||
}
|
||||
|
||||
@@ -302,7 +341,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB
|
||||
}
|
||||
}
|
||||
// Commit and re-open to start with a clean state.
|
||||
root, _ := statedb.Commit(false, false)
|
||||
root, _ := statedb.Commit(0, false, false)
|
||||
statedb, _ = state.New(root, sdb, nil)
|
||||
return statedb
|
||||
}
|
||||
|
||||
@@ -16,22 +16,25 @@ var _ = (*stEnvMarshaling)(nil)
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (s stEnv) MarshalJSON() ([]byte, error) {
|
||||
type stEnv struct {
|
||||
Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"`
|
||||
Random *math.HexOrDecimal256 `json:"currentRandom"`
|
||||
ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"`
|
||||
ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"`
|
||||
ParentGasUsed math.HexOrDecimal64 `json:"parentGasUsed,omitempty"`
|
||||
ParentGasLimit math.HexOrDecimal64 `json:"parentGasLimit,omitempty"`
|
||||
MinBaseFee *math.HexOrDecimal256 `json:"minBaseFee,omitempty"`
|
||||
GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
|
||||
Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
|
||||
Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||
ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"`
|
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||
Ommers []ommer `json:"ommers,omitempty"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
|
||||
ParentUncleHash common.Hash `json:"parentUncleHash"`
|
||||
Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"`
|
||||
Random *math.HexOrDecimal256 `json:"currentRandom"`
|
||||
ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"`
|
||||
ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"`
|
||||
ParentGasUsed math.HexOrDecimal64 `json:"parentGasUsed,omitempty"`
|
||||
ParentGasLimit math.HexOrDecimal64 `json:"parentGasLimit,omitempty"`
|
||||
MinBaseFee *math.HexOrDecimal256 `json:"minBaseFee,omitempty"`
|
||||
GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
|
||||
Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
|
||||
Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||
ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"`
|
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||
Ommers []ommer `json:"ommers,omitempty"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
|
||||
ParentUncleHash common.Hash `json:"parentUncleHash"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas,omitempty"`
|
||||
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
|
||||
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
|
||||
}
|
||||
var enc stEnv
|
||||
enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
|
||||
@@ -50,28 +53,34 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
|
||||
enc.Ommers = s.Ommers
|
||||
enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee)
|
||||
enc.ParentUncleHash = s.ParentUncleHash
|
||||
enc.ExcessBlobGas = (*math.HexOrDecimal64)(s.ExcessBlobGas)
|
||||
enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas)
|
||||
enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (s *stEnv) UnmarshalJSON(input []byte) error {
|
||||
type stEnv struct {
|
||||
Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"`
|
||||
Random *math.HexOrDecimal256 `json:"currentRandom"`
|
||||
ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"`
|
||||
ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"`
|
||||
ParentGasUsed *math.HexOrDecimal64 `json:"parentGasUsed,omitempty"`
|
||||
ParentGasLimit *math.HexOrDecimal64 `json:"parentGasLimit,omitempty"`
|
||||
MinBaseFee *math.HexOrDecimal256 `json:"minBaseFee,omitempty"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
|
||||
Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||
ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"`
|
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||
Ommers []ommer `json:"ommers,omitempty"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
|
||||
ParentUncleHash *common.Hash `json:"parentUncleHash"`
|
||||
Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"`
|
||||
Random *math.HexOrDecimal256 `json:"currentRandom"`
|
||||
ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"`
|
||||
ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"`
|
||||
ParentGasUsed *math.HexOrDecimal64 `json:"parentGasUsed,omitempty"`
|
||||
ParentGasLimit *math.HexOrDecimal64 `json:"parentGasLimit,omitempty"`
|
||||
MinBaseFee *math.HexOrDecimal256 `json:"minBaseFee,omitempty"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
|
||||
Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||
ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"`
|
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||
Ommers []ommer `json:"ommers,omitempty"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
|
||||
ParentUncleHash *common.Hash `json:"parentUncleHash"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas,omitempty"`
|
||||
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
|
||||
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
|
||||
}
|
||||
var dec stEnv
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
@@ -129,5 +138,14 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
|
||||
if dec.ParentUncleHash != nil {
|
||||
s.ParentUncleHash = *dec.ParentUncleHash
|
||||
}
|
||||
if dec.ExcessBlobGas != nil {
|
||||
s.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.ParentExcessBlobGas != nil {
|
||||
s.ParentExcessBlobGas = (*uint64)(dec.ParentExcessBlobGas)
|
||||
}
|
||||
if dec.ParentBlobGasUsed != nil {
|
||||
s.ParentBlobGasUsed = (*uint64)(dec.ParentBlobGasUsed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func Transaction(ctx *cli.Context) error {
|
||||
r.Address = sender
|
||||
}
|
||||
// Check intrinsic gas
|
||||
rules := chainConfig.AvalancheRules(new(big.Int), 0)
|
||||
rules := chainConfig.Rules(new(big.Int), 0)
|
||||
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, rules); err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
||||
+3
-2
@@ -138,6 +138,7 @@ func runCmd(ctx *cli.Context) error {
|
||||
receiver = common.BytesToAddress([]byte("receiver"))
|
||||
genesisConfig *core.Genesis
|
||||
preimages = ctx.Bool(DumpFlag.Name)
|
||||
blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests
|
||||
)
|
||||
if ctx.Bool(MachineFlag.Name) {
|
||||
tracer = logger.NewJSONLogger(logconfig, os.Stdout)
|
||||
@@ -227,6 +228,7 @@ func runCmd(ctx *cli.Context) error {
|
||||
Time: genesisConfig.Timestamp,
|
||||
Coinbase: genesisConfig.Coinbase,
|
||||
BlockNumber: new(big.Int).SetUint64(genesisConfig.Number),
|
||||
BlobHashes: blobHashes,
|
||||
EVMConfig: vm.Config{
|
||||
Tracer: tracer,
|
||||
},
|
||||
@@ -288,8 +290,7 @@ func runCmd(ctx *cli.Context) error {
|
||||
output, leftOverGas, stats, err := timedExec(bench, execFunc)
|
||||
|
||||
if ctx.Bool(DumpFlag.Name) {
|
||||
statedb.Commit(true, false)
|
||||
statedb.IntermediateRoot(true)
|
||||
statedb.Commit(genesisConfig.Number, true, false)
|
||||
fmt.Println(string(statedb.Dump(nil)))
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ func runStateTest(fname string, cfg vm.Config, jsonOut, dump bool) error {
|
||||
// Test failed, mark as so and dump any state to aid debugging
|
||||
result.Pass, result.Error = false, err.Error()
|
||||
if dump && s != nil {
|
||||
s, _ = state.New(*result.Root, s.Database(), nil)
|
||||
dump := s.RawDump(nil)
|
||||
result.State = &dump
|
||||
}
|
||||
|
||||
@@ -276,6 +276,14 @@ func TestT8n(t *testing.T) {
|
||||
// output: t8nOutput{alloc: true, result: true},
|
||||
// expOut: "exp.json",
|
||||
// },
|
||||
{ // Cancun tests
|
||||
base: "./testdata/28",
|
||||
input: t8nInput{
|
||||
"alloc.json", "txs.rlp", "env.json", "Cancun", "",
|
||||
},
|
||||
output: t8nOutput{alloc: true, result: true},
|
||||
expOut: "exp.json",
|
||||
},
|
||||
} {
|
||||
args := []string{"t8n"}
|
||||
args = append(args, tc.output.get()...)
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
## Input transactions in RLP form
|
||||
|
||||
This testdata folder is used to examplify how transaction input can be provided in rlp form.
|
||||
This testdata folder is used to exemplify how transaction input can be provided in rlp form.
|
||||
Please see the README in `evm` folder for how this is performed.
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
These files examplify how to sign a transaction using the pre-EIP155 scheme.
|
||||
These files exemplify how to sign a transaction using the pre-EIP155 scheme.
|
||||
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
|
||||
"balance" : "0x016345785d8a0000",
|
||||
"code" : "0x",
|
||||
"nonce" : "0x00",
|
||||
"storage" : {
|
||||
}
|
||||
},
|
||||
"0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
|
||||
"balance" : "0x016345785d8a0000",
|
||||
"code" : "0x60004960015500",
|
||||
"nonce" : "0x00",
|
||||
"storage" : {
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
|
||||
"currentNumber" : "0x01",
|
||||
"currentTimestamp" : "0x079e",
|
||||
"currentGasLimit" : "0x7fffffffffffffff",
|
||||
"previousHash" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6",
|
||||
"currentBlobGasUsed" : "0x00",
|
||||
"parentTimestamp" : "0x03b6",
|
||||
"parentDifficulty" : "0x00",
|
||||
"parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"currentRandom" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"withdrawals" : [
|
||||
],
|
||||
"parentBaseFee" : "0x0a",
|
||||
"parentGasUsed" : "0x00",
|
||||
"parentGasLimit" : "0x7fffffffffffffff",
|
||||
"parentExcessBlobGas" : "0x00",
|
||||
"parentBlobGasUsed" : "0x00",
|
||||
"blockHashes" : {
|
||||
"0" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6"
|
||||
},
|
||||
"minBaseFee" : "0x9"
|
||||
}
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"alloc": {
|
||||
"0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": {
|
||||
"balance": "0x73c57"
|
||||
},
|
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||
"balance": "0x16345785d80c3a9",
|
||||
"nonce": "0x1"
|
||||
},
|
||||
"0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||
"code": "0x60004960015500",
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x01a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
|
||||
},
|
||||
"balance": "0x16345785d8a0000"
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"stateRoot": "0xabcbb1d3be8aee044a219dd181fe6f2c2482749b9da95d15358ba7af9b43c372",
|
||||
"txRoot": "0x4409cc4b699384ba5f8248d92b784713610c5ff9c1de51e9239da0dac76de9ce",
|
||||
"receiptsRoot": "0xbff643da765981266133094092d98c81d2ac8e9a83a7bbda46c3d736f1f874ac",
|
||||
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"receipts": [
|
||||
{
|
||||
"type": "0x3",
|
||||
"root": "0x",
|
||||
"status": "0x1",
|
||||
"cumulativeGasUsed": "0xa865",
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"logs": null,
|
||||
"transactionHash": "0x7508d7139d002a4b3a26a4f12dec0d87cb46075c78bf77a38b569a133b509262",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0xa865",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x0"
|
||||
}
|
||||
],
|
||||
"currentDifficulty": null,
|
||||
"gasUsed": "0xa865",
|
||||
"currentBaseFee": "0x9",
|
||||
"currentExcessBlobGas": "0x0",
|
||||
"currentBlobGasUsed": "0x20000"
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
"0xf88bb88903f8860180026483061a8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b8080c00ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d801a025e16bb498552165016751911c3608d79000ab89dc3100776e729e6ea13091c7a03acacff7fc0cff6eda8a927dec93ca17765e1ee6cbc06c5954ce102e097c01d2"
|
||||
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
||||
These files examplify a transition where a transaction (excuted on block 5) requests
|
||||
These files exemplify a transition where a transaction (executed on block 5) requests
|
||||
the blockhash for block `1`.
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
These files examplify a transition where a transaction (excuted on block 5) requests
|
||||
These files exemplify a transition where a transaction (executed on block 5) requests
|
||||
the blockhash for block `4`, but where the hash for that block is missing.
|
||||
It's expected that executing these should cause `exit` with errorcode `4`.
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
These files examplify a transition where there are no transcations, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2).
|
||||
These files exemplify a transition where there are no transactions, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2).
|
||||
@@ -280,7 +280,7 @@ To make `t8n` apply these, the following inputs are required:
|
||||
- For ethash, it is `5000000000000000000` `wei`,
|
||||
- If this is not defined, mining rewards are not applied,
|
||||
- A value of `0` is valid, and causes accounts to be 'touched'.
|
||||
- For each ommer, the tool needs to be given an `addres\` and a `delta`. This
|
||||
- For each ommer, the tool needs to be given an `address\` and a `delta`. This
|
||||
is done via the `ommers` field in `env`.
|
||||
|
||||
Note: the tool does not verify that e.g. the normal uncle rules apply,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package utils contains internal helper functions for go-ethereum commands.
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Fatalf formats a message to standard error and exits the program.
|
||||
// The message is also printed to standard output if standard error
|
||||
// is redirected to a different file.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
w := io.MultiWriter(os.Stdout, os.Stderr)
|
||||
if runtime.GOOS == "windows" {
|
||||
// The SameFile check below doesn't work on Windows.
|
||||
// stdout is unlikely to get redirected though, so just print there.
|
||||
w = os.Stdout
|
||||
} else {
|
||||
outf, _ := os.Stdout.Stat()
|
||||
errf, _ := os.Stderr.Stat()
|
||||
if outf != nil && errf != nil && os.SameFile(outf, errf) {
|
||||
w = os.Stderr
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "Fatal: "+format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -19,34 +19,11 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Fatalf formats a message to standard error and exits the program.
|
||||
// The message is also printed to standard output if standard error
|
||||
// is redirected to a different file.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
w := io.MultiWriter(os.Stdout, os.Stderr)
|
||||
if runtime.GOOS == "windows" {
|
||||
// The SameFile check below doesn't work on Windows.
|
||||
// stdout is unlikely to get redirected though, so just print there.
|
||||
w = os.Stdout
|
||||
} else {
|
||||
outf, _ := os.Stdout.Stat()
|
||||
errf, _ := os.Stderr.Stat()
|
||||
if outf != nil && errf != nil && os.SameFile(outf, errf) {
|
||||
w = os.Stderr
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "Fatal: "+format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// CheckExclusive verifies that only a single instance of the provided flags was
|
||||
// set by the user. Each flag might optionally be followed by a string type to
|
||||
// specialize it further.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/ava-labs/avalanchego/utils/timer/mockable"
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
"github.com/ava-labs/subnet-evm/consensus/misc/eip4844"
|
||||
"github.com/ava-labs/subnet-evm/core/state"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
@@ -233,13 +234,18 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header
|
||||
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
|
||||
return consensus.ErrInvalidNumber
|
||||
}
|
||||
// Verify the existence / non-existence of excessDataGas
|
||||
cancun := chain.Config().IsCancun(header.Time)
|
||||
if cancun && header.ExcessDataGas == nil {
|
||||
return errors.New("missing excessDataGas")
|
||||
// Verify the existence / non-existence of excessBlobGas
|
||||
cancun := chain.Config().IsCancun(header.Number, header.Time)
|
||||
if !cancun && header.ExcessBlobGas != nil {
|
||||
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
|
||||
}
|
||||
if !cancun && header.ExcessDataGas != nil {
|
||||
return fmt.Errorf("invalid excessDataGas: have %d, expected nil", header.ExcessDataGas)
|
||||
if !cancun && header.BlobGasUsed != nil {
|
||||
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
|
||||
}
|
||||
if cancun {
|
||||
if err := eip4844.VerifyEIP4844Header(parent, header); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2023 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 misc
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
)
|
||||
|
||||
var (
|
||||
minDataGasPrice = big.NewInt(params.BlobTxMinDataGasprice)
|
||||
dataGaspriceUpdateFraction = big.NewInt(params.BlobTxDataGaspriceUpdateFraction)
|
||||
)
|
||||
|
||||
// CalcBlobFee calculates the blobfee from the header's excess data gas field.
|
||||
func CalcBlobFee(excessDataGas *big.Int) *big.Int {
|
||||
// If this block does not yet have EIP-4844 enabled, return the starting fee
|
||||
if excessDataGas == nil {
|
||||
return big.NewInt(params.BlobTxMinDataGasprice)
|
||||
}
|
||||
return fakeExponential(minDataGasPrice, excessDataGas, dataGaspriceUpdateFraction)
|
||||
}
|
||||
|
||||
// fakeExponential approximates factor * e ** (numerator / denominator) using
|
||||
// Taylor expansion.
|
||||
func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
|
||||
var (
|
||||
output = new(big.Int)
|
||||
accum = new(big.Int).Mul(factor, denominator)
|
||||
)
|
||||
for i := 1; accum.Sign() > 0; i++ {
|
||||
output.Add(output, accum)
|
||||
|
||||
accum.Mul(accum, numerator)
|
||||
accum.Div(accum, denominator)
|
||||
accum.Div(accum, big.NewInt(int64(i)))
|
||||
}
|
||||
return output.Div(output, denominator)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// (c) 2024, 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 2023 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 eip4844
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
)
|
||||
|
||||
var (
|
||||
minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
|
||||
blobGaspriceUpdateFraction = big.NewInt(params.BlobTxBlobGaspriceUpdateFraction)
|
||||
)
|
||||
|
||||
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
|
||||
// if the current block contains no transactions, the excessBlobGas is updated
|
||||
// accordingly.
|
||||
func VerifyEIP4844Header(parent, header *types.Header) error {
|
||||
// Verify the header is not malformed
|
||||
if header.ExcessBlobGas == nil {
|
||||
return errors.New("header is missing excessBlobGas")
|
||||
}
|
||||
if header.BlobGasUsed == nil {
|
||||
return errors.New("header is missing blobGasUsed")
|
||||
}
|
||||
// Verify that the blob gas used remains within reasonable limits.
|
||||
if *header.BlobGasUsed > params.BlobTxMaxBlobGasPerBlock {
|
||||
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, params.BlobTxMaxBlobGasPerBlock)
|
||||
}
|
||||
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
|
||||
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
|
||||
}
|
||||
// Verify the excessBlobGas is correct based on the parent header
|
||||
var (
|
||||
parentExcessBlobGas uint64
|
||||
parentBlobGasUsed uint64
|
||||
)
|
||||
if parent.ExcessBlobGas != nil {
|
||||
parentExcessBlobGas = *parent.ExcessBlobGas
|
||||
parentBlobGasUsed = *parent.BlobGasUsed
|
||||
}
|
||||
expectedExcessBlobGas := CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed)
|
||||
if *header.ExcessBlobGas != expectedExcessBlobGas {
|
||||
return fmt.Errorf("invalid excessBlobGas: have %d, want %d, parent excessBlobGas %d, parent blobDataUsed %d",
|
||||
*header.ExcessBlobGas, expectedExcessBlobGas, parentExcessBlobGas, parentBlobGasUsed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalcExcessBlobGas calculates the excess blob gas after applying the set of
|
||||
// blobs on top of the excess blob gas.
|
||||
func CalcExcessBlobGas(parentExcessBlobGas uint64, parentBlobGasUsed uint64) uint64 {
|
||||
excessBlobGas := parentExcessBlobGas + parentBlobGasUsed
|
||||
if excessBlobGas < params.BlobTxTargetBlobGasPerBlock {
|
||||
return 0
|
||||
}
|
||||
return excessBlobGas - params.BlobTxTargetBlobGasPerBlock
|
||||
}
|
||||
|
||||
// CalcBlobFee calculates the blobfee from the header's excess blob gas field.
|
||||
func CalcBlobFee(excessBlobGas uint64) *big.Int {
|
||||
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), blobGaspriceUpdateFraction)
|
||||
}
|
||||
|
||||
// fakeExponential approximates factor * e ** (numerator / denominator) using
|
||||
// Taylor expansion.
|
||||
func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
|
||||
var (
|
||||
output = new(big.Int)
|
||||
accum = new(big.Int).Mul(factor, denominator)
|
||||
)
|
||||
for i := 1; accum.Sign() > 0; i++ {
|
||||
output.Add(output, accum)
|
||||
|
||||
accum.Mul(accum, numerator)
|
||||
accum.Div(accum, denominator)
|
||||
accum.Div(accum, big.NewInt(int64(i)))
|
||||
}
|
||||
return output.Div(output, denominator)
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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 misc
|
||||
package eip4844
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -24,9 +24,42 @@ import (
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
)
|
||||
|
||||
func TestCalcExcessBlobGas(t *testing.T) {
|
||||
var tests = []struct {
|
||||
excess uint64
|
||||
blobs uint64
|
||||
want uint64
|
||||
}{
|
||||
// The excess blob gas should not increase from zero if the used blob
|
||||
// slots are below - or equal - to the target.
|
||||
{0, 0, 0},
|
||||
{0, 1, 0},
|
||||
{0, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, 0},
|
||||
|
||||
// If the target blob gas is exceeded, the excessBlobGas should increase
|
||||
// by however much it was overshot
|
||||
{0, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob},
|
||||
{1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob + 1},
|
||||
{1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 2, 2*params.BlobTxBlobGasPerBlob + 1},
|
||||
|
||||
// The excess blob gas should decrease by however much the target was
|
||||
// under-shot, capped at zero.
|
||||
{params.BlobTxTargetBlobGasPerBlock, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, params.BlobTxTargetBlobGasPerBlock},
|
||||
{params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, params.BlobTxBlobGasPerBlob},
|
||||
{params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 2, 0},
|
||||
{params.BlobTxBlobGasPerBlob - 1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, 0},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
result := CalcExcessBlobGas(tt.excess, tt.blobs*params.BlobTxBlobGasPerBlob)
|
||||
if result != tt.want {
|
||||
t.Errorf("excess blob gas mismatch: have %v, want %v", result, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcBlobFee(t *testing.T) {
|
||||
tests := []struct {
|
||||
excessDataGas int64
|
||||
excessBlobGas uint64
|
||||
blobfee int64
|
||||
}{
|
||||
{0, 1},
|
||||
@@ -34,12 +67,8 @@ func TestCalcBlobFee(t *testing.T) {
|
||||
{1542707, 2},
|
||||
{10 * 1024 * 1024, 111},
|
||||
}
|
||||
have := CalcBlobFee(nil)
|
||||
if have.Int64() != params.BlobTxMinDataGasprice {
|
||||
t.Errorf("nil test: blobfee mismatch: have %v, want %v", have, params.BlobTxMinDataGasprice)
|
||||
}
|
||||
for i, tt := range tests {
|
||||
have := CalcBlobFee(big.NewInt(tt.excessDataGas))
|
||||
have := CalcBlobFee(tt.excessBlobGas)
|
||||
if have.Int64() != tt.blobfee {
|
||||
t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee)
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
@@ -77,6 +78,23 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
|
||||
return fmt.Errorf("transaction root hash mismatch (header value %x, calculated %x)", header.TxHash, hash)
|
||||
}
|
||||
// Blob transactions may be present after the Cancun fork.
|
||||
var blobs int
|
||||
for _, tx := range block.Transactions() {
|
||||
// Count the number of blobs to validate against the header's blobGasUsed
|
||||
blobs += len(tx.BlobHashes())
|
||||
// The individual checks for blob validity (version-check + not empty)
|
||||
// happens in the state_transition check.
|
||||
}
|
||||
if header.BlobGasUsed != nil {
|
||||
if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated
|
||||
return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob)
|
||||
}
|
||||
} else {
|
||||
if blobs > 0 {
|
||||
return errors.New("data blobs present in block body")
|
||||
}
|
||||
}
|
||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||
return consensus.ErrUnknownAncestor
|
||||
|
||||
+40
-46
@@ -41,6 +41,7 @@ import (
|
||||
|
||||
"github.com/ava-labs/subnet-evm/commontype"
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
"github.com/ava-labs/subnet-evm/consensus/misc/eip4844"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/state"
|
||||
"github.com/ava-labs/subnet-evm/core/state/snapshot"
|
||||
@@ -101,6 +102,8 @@ var (
|
||||
|
||||
errFutureBlockUnsupported = errors.New("future block insertion not supported")
|
||||
errCacheConfigNotSpecified = errors.New("must specify cache config")
|
||||
errInvalidOldChain = errors.New("invalid old chain")
|
||||
errInvalidNewChain = errors.New("invalid new chain")
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -143,7 +146,7 @@ const (
|
||||
|
||||
// trieCleanCacheStatsNamespace is the namespace to surface stats from the trie
|
||||
// clean cache's underlying fastcache.
|
||||
trieCleanCacheStatsNamespace = "trie/memcache/clean/fastcache"
|
||||
trieCleanCacheStatsNamespace = "hashdb/memcache/clean/fastcache"
|
||||
)
|
||||
|
||||
// cacheableFeeConfig encapsulates fee configuration itself and the block number that it has changed at,
|
||||
@@ -163,25 +166,23 @@ type cacheableCoinbaseConfig struct {
|
||||
// CacheConfig contains the configuration values for the trie database
|
||||
// that's resident in a blockchain.
|
||||
type CacheConfig struct {
|
||||
TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
TrieCleanJournal string // Disk journal for saving clean cache entries.
|
||||
TrieCleanRejournal time.Duration // Time interval to dump clean cache to disk periodically
|
||||
TrieDirtyLimit int // Memory limit (MB) at which to block on insert and force a flush of dirty trie nodes to disk
|
||||
TrieDirtyCommitTarget int // Memory limit (MB) to target for the dirties cache before invoking commit
|
||||
TriePrefetcherParallelism int // Max concurrent disk reads trie prefetcher should perform at once
|
||||
CommitInterval uint64 // Commit the trie every [CommitInterval] blocks.
|
||||
Pruning bool // Whether to disable trie write caching and GC altogether (archive node)
|
||||
AcceptorQueueLimit int // Blocks to queue before blocking during acceptance
|
||||
PopulateMissingTries *uint64 // If non-nil, sets the starting height for re-generating historical tries.
|
||||
PopulateMissingTriesParallelism int // Number of readers to use when trying to populate missing tries.
|
||||
AllowMissingTries bool // Whether to allow an archive node to run with pruning enabled
|
||||
SnapshotDelayInit bool // Whether to initialize snapshots on startup or wait for external call
|
||||
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
|
||||
SnapshotVerify bool // Verify generated snapshots
|
||||
Preimages bool // Whether to store preimage of trie key to the disk
|
||||
AcceptedCacheSize int // Depth of accepted headers cache and accepted logs cache at the accepted tip
|
||||
TxLookupLimit uint64 // Number of recent blocks for which to maintain transaction lookup indices
|
||||
SkipTxIndexing bool // Whether to skip transaction indexing
|
||||
TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
TrieDirtyLimit int // Memory limit (MB) at which to block on insert and force a flush of dirty trie nodes to disk
|
||||
TrieDirtyCommitTarget int // Memory limit (MB) to target for the dirties cache before invoking commit
|
||||
TriePrefetcherParallelism int // Max concurrent disk reads trie prefetcher should perform at once
|
||||
CommitInterval uint64 // Commit the trie every [CommitInterval] blocks.
|
||||
Pruning bool // Whether to disable trie write caching and GC altogether (archive node)
|
||||
AcceptorQueueLimit int // Blocks to queue before blocking during acceptance
|
||||
PopulateMissingTries *uint64 // If non-nil, sets the starting height for re-generating historical tries.
|
||||
PopulateMissingTriesParallelism int // Number of readers to use when trying to populate missing tries.
|
||||
AllowMissingTries bool // Whether to allow an archive node to run with pruning enabled
|
||||
SnapshotDelayInit bool // Whether to initialize snapshots on startup or wait for external call
|
||||
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
|
||||
SnapshotVerify bool // Verify generated snapshots
|
||||
Preimages bool // Whether to store preimage of trie key to the disk
|
||||
AcceptedCacheSize int // Depth of accepted headers cache and accepted logs cache at the accepted tip
|
||||
TxLookupLimit uint64 // Number of recent blocks for which to maintain transaction lookup indices
|
||||
SkipTxIndexing bool // Whether to skip transaction indexing
|
||||
|
||||
SnapshotNoBuild bool // Whether the background generation is allowed
|
||||
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
||||
@@ -315,7 +316,6 @@ func NewBlockChain(
|
||||
// Open trie database with provided config
|
||||
triedb := trie.NewDatabaseWithConfig(db, &trie.Config{
|
||||
Cache: cacheConfig.TrieCleanLimit,
|
||||
Journal: cacheConfig.TrieCleanJournal,
|
||||
Preimages: cacheConfig.Preimages,
|
||||
StatsPrefix: trieCleanCacheStatsNamespace,
|
||||
})
|
||||
@@ -416,17 +416,6 @@ func NewBlockChain(
|
||||
// Start processing accepted blocks effects in the background
|
||||
go bc.startAcceptor()
|
||||
|
||||
// If periodic cache journal is required, spin it up.
|
||||
if bc.cacheConfig.TrieCleanRejournal > 0 && len(bc.cacheConfig.TrieCleanJournal) > 0 {
|
||||
log.Info("Starting to save trie clean cache periodically", "journalDir", bc.cacheConfig.TrieCleanJournal, "freq", bc.cacheConfig.TrieCleanRejournal)
|
||||
|
||||
bc.wg.Add(1)
|
||||
go func() {
|
||||
defer bc.wg.Done()
|
||||
bc.triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit)
|
||||
}()
|
||||
}
|
||||
|
||||
// Start tx indexer/unindexer if required.
|
||||
if bc.cacheConfig.TxLookupLimit != 0 {
|
||||
bc.wg.Add(1)
|
||||
@@ -801,7 +790,7 @@ func (bc *BlockChain) ExportCallback(callback func(block *types.Block) error, fi
|
||||
return fmt.Errorf("export failed on #%d: not found", nr)
|
||||
}
|
||||
if nr > first && block.ParentHash() != parentHash {
|
||||
return fmt.Errorf("export failed: chain reorg during export")
|
||||
return errors.New("export failed: chain reorg during export")
|
||||
}
|
||||
parentHash = block.Hash()
|
||||
if err := callback(block); err != nil {
|
||||
@@ -989,7 +978,6 @@ func (bc *BlockChain) Stop() {
|
||||
if err := bc.stateCache.TrieDB().Close(); err != nil {
|
||||
log.Error("Failed to close trie db", "err", err)
|
||||
}
|
||||
|
||||
log.Info("Blockchain stopped")
|
||||
}
|
||||
|
||||
@@ -1229,9 +1217,9 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
// diff layer for the block.
|
||||
var err error
|
||||
if bc.snaps == nil {
|
||||
_, err = state.Commit(bc.chainConfig.IsEIP158(block.Number()), true)
|
||||
_, err = state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), true)
|
||||
} else {
|
||||
_, err = state.CommitWithSnap(bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash(), true)
|
||||
_, err = state.CommitWithSnap(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash(), true)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1458,8 +1446,15 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error {
|
||||
// collectUnflattenedLogs collects the logs that were generated or removed during
|
||||
// the processing of a block.
|
||||
func (bc *BlockChain) collectUnflattenedLogs(b *types.Block, removed bool) [][]*types.Log {
|
||||
var blobGasPrice *big.Int
|
||||
excessBlobGas := b.ExcessBlobGas()
|
||||
if excessBlobGas != nil {
|
||||
blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas)
|
||||
}
|
||||
receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64())
|
||||
receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.BaseFee(), b.Transactions())
|
||||
if err := receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.BaseFee(), blobGasPrice, b.Transactions()); err != nil {
|
||||
log.Error("Failed to derive block receipts fields", "hash", b.Hash(), "number", b.NumberU64(), "err", err)
|
||||
}
|
||||
|
||||
// Note: gross but this needs to be initialized here because returning nil will be treated specially as an incorrect
|
||||
// error case downstream.
|
||||
@@ -1467,11 +1462,10 @@ func (bc *BlockChain) collectUnflattenedLogs(b *types.Block, removed bool) [][]*
|
||||
for i, receipt := range receipts {
|
||||
receiptLogs := make([]*types.Log, len(receipt.Logs))
|
||||
for i, log := range receipt.Logs {
|
||||
l := *log
|
||||
if removed {
|
||||
l.Removed = true
|
||||
log.Removed = true
|
||||
}
|
||||
receiptLogs[i] = &l
|
||||
receiptLogs[i] = log
|
||||
}
|
||||
logs[i] = receiptLogs
|
||||
}
|
||||
@@ -1513,10 +1507,10 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
|
||||
}
|
||||
}
|
||||
if oldBlock == nil {
|
||||
return errors.New("invalid old chain")
|
||||
return errInvalidOldChain
|
||||
}
|
||||
if newBlock == nil {
|
||||
return errors.New("invalid new chain")
|
||||
return errInvalidNewChain
|
||||
}
|
||||
// Both sides of the reorg are at the same number, reduce both until the common
|
||||
// ancestor is found
|
||||
@@ -1533,11 +1527,11 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
|
||||
// Step back with both chains
|
||||
oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1)
|
||||
if oldBlock == nil {
|
||||
return fmt.Errorf("invalid old chain")
|
||||
return errInvalidOldChain
|
||||
}
|
||||
newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1)
|
||||
if newBlock == nil {
|
||||
return fmt.Errorf("invalid new chain")
|
||||
return errInvalidNewChain
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1772,9 +1766,9 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block)
|
||||
// If snapshots are enabled, call CommitWithSnaps to explicitly create a snapshot
|
||||
// diff layer for the block.
|
||||
if bc.snaps == nil {
|
||||
return statedb.Commit(bc.chainConfig.IsEIP158(current.Number()), false)
|
||||
return statedb.Commit(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), false)
|
||||
}
|
||||
return statedb.CommitWithSnap(bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash(), false)
|
||||
return statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash(), false)
|
||||
}
|
||||
|
||||
// initSnapshot instantiates a Snapshot instance and adds it to [bc]
|
||||
|
||||
@@ -21,8 +21,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -83,100 +81,6 @@ func TestArchiveBlockChain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// awaitWatcherEventsSubside waits for at least one event on [watcher] and then waits
|
||||
// for at least [subsideTimeout] before returning
|
||||
func awaitWatcherEventsSubside(watcher *fsnotify.Watcher, subsideTimeout time.Duration) {
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-watcher.Events:
|
||||
case <-watcher.Errors:
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-watcher.Events:
|
||||
case <-watcher.Errors:
|
||||
return
|
||||
case <-time.After(subsideTimeout):
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestTrieCleanJournal(t *testing.T) {
|
||||
if os.Getenv("RUN_FLAKY_TESTS") != "true" {
|
||||
t.Skip("FLAKY")
|
||||
}
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
trieCleanJournal := t.TempDir()
|
||||
trieCleanJournalWatcher, err := fsnotify.NewWatcher()
|
||||
require.NoError(err)
|
||||
defer func() {
|
||||
assert.NoError(trieCleanJournalWatcher.Close())
|
||||
}()
|
||||
require.NoError(trieCleanJournalWatcher.Add(trieCleanJournal))
|
||||
|
||||
create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) {
|
||||
config := *archiveConfig
|
||||
config.TrieCleanJournal = trieCleanJournal
|
||||
config.TrieCleanRejournal = 100 * time.Millisecond
|
||||
return createBlockChain(db, &config, gspec, lastAcceptedHash)
|
||||
}
|
||||
|
||||
var (
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||
chainDB = rawdb.NewMemoryDatabase()
|
||||
)
|
||||
|
||||
// Ensure that key1 has some funds in the genesis block.
|
||||
genesisBalance := big.NewInt(1000000)
|
||||
gspec := &Genesis{
|
||||
Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)},
|
||||
Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}},
|
||||
}
|
||||
|
||||
blockchain, err := create(chainDB, gspec, common.Hash{})
|
||||
require.NoError(err)
|
||||
defer blockchain.Stop()
|
||||
|
||||
// This call generates a chain of 3 blocks.
|
||||
signer := types.HomesteadSigner{}
|
||||
_, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1)
|
||||
gen.AddTx(tx)
|
||||
})
|
||||
require.NoError(err)
|
||||
|
||||
// Insert and accept the generated chain
|
||||
_, err = blockchain.InsertChain(chain)
|
||||
require.NoError(err)
|
||||
|
||||
for _, block := range chain {
|
||||
require.NoError(blockchain.Accept(block))
|
||||
}
|
||||
blockchain.DrainAcceptorQueue()
|
||||
|
||||
awaitWatcherEventsSubside(trieCleanJournalWatcher, time.Second)
|
||||
// Assert that a new file is created in the trie clean journal
|
||||
dirEntries, err := os.ReadDir(trieCleanJournal)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(dirEntries)
|
||||
}
|
||||
|
||||
func TestArchiveBlockChainSnapsDisabled(t *testing.T) {
|
||||
create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) {
|
||||
return createBlockChain(
|
||||
@@ -375,7 +279,6 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) {
|
||||
prunerConfig := pruner.Config{
|
||||
Datadir: tempDir,
|
||||
BloomSize: 256,
|
||||
Cachedir: pruningConfig.TrieCleanJournal,
|
||||
}
|
||||
|
||||
pruner, err := pruner.NewPruner(db, prunerConfig)
|
||||
|
||||
@@ -29,6 +29,7 @@ package core
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -413,7 +414,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
||||
if header == nil {
|
||||
return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4])
|
||||
} else if header.ParentHash != lastHead {
|
||||
return common.Hash{}, fmt.Errorf("chain reorged during section processing")
|
||||
return common.Hash{}, errors.New("chain reorged during section processing")
|
||||
}
|
||||
if err := c.backend.Process(c.ctx, header); err != nil {
|
||||
return common.Hash{}, err
|
||||
|
||||
@@ -271,7 +271,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||
}
|
||||
|
||||
// Write state changes to db
|
||||
root, err := statedb.Commit(config.IsEIP158(b.header.Number), false)
|
||||
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("state write error: %v", err))
|
||||
}
|
||||
@@ -335,7 +335,6 @@ func makeHeader(chain consensus.ChainReader, config *params.ChainConfig, parent
|
||||
Number: new(big.Int).Add(parent.Number(), common.Big1),
|
||||
Time: time,
|
||||
}
|
||||
|
||||
if chain.Config().IsSubnetEVM(time) {
|
||||
feeConfig, _, err := chain.GetFeeConfigAt(parent.Header())
|
||||
if err != nil {
|
||||
|
||||
@@ -105,4 +105,8 @@ var (
|
||||
|
||||
// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
|
||||
ErrSenderNoEOA = errors.New("sender not an eoa")
|
||||
|
||||
// ErrBlobFeeCapTooLow is returned if the transaction fee cap is less than the
|
||||
// blob gas fee of the block.
|
||||
ErrBlobFeeCapTooLow = errors.New("max fee per blob gas less than block blob gas fee")
|
||||
)
|
||||
|
||||
+4
-2
@@ -102,14 +102,16 @@ func newEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||
BaseFee: baseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
ExcessBlobGas: header.ExcessBlobGas,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEVMTxContext creates a new transaction context for a single transaction.
|
||||
func NewEVMTxContext(msg *Message) vm.TxContext {
|
||||
return vm.TxContext{
|
||||
Origin: msg.From,
|
||||
GasPrice: new(big.Int).Set(msg.GasPrice),
|
||||
Origin: msg.From,
|
||||
GasPrice: new(big.Int).Set(msg.GasPrice),
|
||||
BlobHashes: msg.BlobHashes,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,13 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
|
||||
Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"`
|
||||
AirdropHash common.Hash `json:"airdropHash"`
|
||||
AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"`
|
||||
AirdropData []byte `json:"-"`
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
}
|
||||
var enc Genesis
|
||||
enc.Config = g.Config
|
||||
@@ -51,10 +54,13 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
enc.AirdropHash = g.AirdropHash
|
||||
enc.AirdropAmount = (*math.HexOrDecimal256)(g.AirdropAmount)
|
||||
enc.AirdropData = g.AirdropData
|
||||
enc.Number = math.HexOrDecimal64(g.Number)
|
||||
enc.GasUsed = math.HexOrDecimal64(g.GasUsed)
|
||||
enc.ParentHash = g.ParentHash
|
||||
enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee)
|
||||
enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas)
|
||||
enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
@@ -72,10 +78,13 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
|
||||
Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"`
|
||||
AirdropHash *common.Hash `json:"airdropHash"`
|
||||
AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"`
|
||||
AirdropData []byte `json:"-"`
|
||||
Number *math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash *common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
}
|
||||
var dec Genesis
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
@@ -120,6 +129,9 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
|
||||
if dec.AirdropAmount != nil {
|
||||
g.AirdropAmount = (*big.Int)(dec.AirdropAmount)
|
||||
}
|
||||
if dec.AirdropData != nil {
|
||||
g.AirdropData = dec.AirdropData
|
||||
}
|
||||
if dec.Number != nil {
|
||||
g.Number = uint64(*dec.Number)
|
||||
}
|
||||
@@ -132,5 +144,11 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
|
||||
if dec.BaseFee != nil {
|
||||
g.BaseFee = (*big.Int)(dec.BaseFee)
|
||||
}
|
||||
if dec.ExcessBlobGas != nil {
|
||||
g.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.BlobGasUsed != nil {
|
||||
g.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+29
-11
@@ -77,10 +77,12 @@ type Genesis struct {
|
||||
|
||||
// These fields are used for consensus tests. Please don't use them
|
||||
// in actual genesis blocks.
|
||||
Number uint64 `json:"number"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *big.Int `json:"baseFeePerGas"`
|
||||
Number uint64 `json:"number"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
|
||||
}
|
||||
|
||||
// GenesisAlloc specifies the initial state that is part of the genesis block.
|
||||
@@ -119,6 +121,8 @@ type genesisSpecMarshaling struct {
|
||||
BaseFee *math.HexOrDecimal256
|
||||
Alloc map[common.UnprefixedAddress]GenesisAccount
|
||||
AirdropAmount *math.HexOrDecimal256
|
||||
ExcessBlobGas *math.HexOrDecimal64
|
||||
BlobGasUsed *math.HexOrDecimal64
|
||||
}
|
||||
|
||||
type genesisAccountMarshaling struct {
|
||||
@@ -232,7 +236,7 @@ func SetupGenesisBlock(
|
||||
// when we start syncing from scratch, the last accepted block
|
||||
// will be genesis block
|
||||
if lastBlock == nil {
|
||||
return newcfg, common.Hash{}, fmt.Errorf("missing last accepted block")
|
||||
return newcfg, common.Hash{}, errors.New("missing last accepted block")
|
||||
}
|
||||
height := lastBlock.NumberU64()
|
||||
timestamp := lastBlock.Time()
|
||||
@@ -325,14 +329,28 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *trie.Database) *types.Block
|
||||
if g.Difficulty == nil {
|
||||
head.Difficulty = params.GenesisDifficulty
|
||||
}
|
||||
if g.Config != nil && g.Config.IsSubnetEVM(0) {
|
||||
if g.BaseFee != nil {
|
||||
head.BaseFee = g.BaseFee
|
||||
} else {
|
||||
head.BaseFee = new(big.Int).Set(g.Config.FeeConfig.MinBaseFee)
|
||||
if conf := g.Config; conf != nil {
|
||||
num := new(big.Int).SetUint64(g.Number)
|
||||
if conf.IsSubnetEVM(g.Timestamp) {
|
||||
if g.BaseFee != nil {
|
||||
head.BaseFee = g.BaseFee
|
||||
} else {
|
||||
head.BaseFee = new(big.Int).Set(g.Config.FeeConfig.MinBaseFee)
|
||||
}
|
||||
}
|
||||
if conf.IsCancun(num, g.Timestamp) {
|
||||
head.ExcessBlobGas = g.ExcessBlobGas
|
||||
head.BlobGasUsed = g.BlobGasUsed
|
||||
if head.ExcessBlobGas == nil {
|
||||
head.ExcessBlobGas = new(uint64)
|
||||
}
|
||||
if head.BlobGasUsed == nil {
|
||||
head.BlobGasUsed = new(uint64)
|
||||
}
|
||||
}
|
||||
}
|
||||
statedb.Commit(false, false)
|
||||
|
||||
statedb.Commit(0, false, false)
|
||||
// Commit newly generated states into disk if it's not empty.
|
||||
if root != types.EmptyRootHash {
|
||||
if err := triedb.Commit(root, true); err != nil {
|
||||
|
||||
@@ -289,9 +289,9 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) {
|
||||
|
||||
func TestGenesisWriteUpgradesRegression(t *testing.T) {
|
||||
require := require.New(t)
|
||||
testConfig := *params.TestChainConfig
|
||||
config := *params.TestChainConfig
|
||||
genesis := &Genesis{
|
||||
Config: &testConfig,
|
||||
Config: &config,
|
||||
Alloc: GenesisAlloc{
|
||||
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
|
||||
},
|
||||
|
||||
+8
-12
@@ -40,32 +40,28 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type allocItem struct{ Addr, Balance *big.Int }
|
||||
|
||||
type allocList []allocItem
|
||||
|
||||
func (a allocList) Len() int { return len(a) }
|
||||
func (a allocList) Less(i, j int) bool { return a[i].Addr.Cmp(a[j].Addr) < 0 }
|
||||
func (a allocList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func makelist(g *core.Genesis) allocList {
|
||||
a := make(allocList, 0, len(g.Alloc))
|
||||
func makelist(g *core.Genesis) []allocItem {
|
||||
items := make([]allocItem, 0, len(g.Alloc))
|
||||
for addr, account := range g.Alloc {
|
||||
if len(account.Storage) > 0 || len(account.Code) > 0 || account.Nonce != 0 {
|
||||
panic(fmt.Sprintf("can't encode account %x", addr))
|
||||
}
|
||||
bigAddr := new(big.Int).SetBytes(addr.Bytes())
|
||||
a = append(a, allocItem{bigAddr, account.Balance})
|
||||
items = append(items, allocItem{bigAddr, account.Balance})
|
||||
}
|
||||
sort.Sort(a)
|
||||
return a
|
||||
slices.SortFunc(items, func(a, b allocItem) bool {
|
||||
return a.Addr.Cmp(b.Addr) < 0
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
func makealloc(g *core.Genesis) string {
|
||||
|
||||
@@ -297,7 +297,7 @@ 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.AvalancheRules(common.Big0, 0)
|
||||
rules := params.TestChainConfig.Rules(common.Big0, 0)
|
||||
if test.createPredicates != nil {
|
||||
for address, predicater := range test.createPredicates(t) {
|
||||
rules.Predicaters[address] = predicater
|
||||
@@ -423,7 +423,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.AvalancheRules(common.Big0, 0)
|
||||
rules := params.TestChainConfig.Rules(common.Big0, 0)
|
||||
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
|
||||
predicater.EXPECT().PredicateGas(gomock.Any()).Return(uint64(0), nil).Times(len(test.testTuple))
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus/misc/eip4844"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -85,7 +86,7 @@ type NumberHash struct {
|
||||
Hash common.Hash
|
||||
}
|
||||
|
||||
// ReadAllHashesInRange retrieves all the hashes assigned to blocks at a certain
|
||||
// ReadAllHashesInRange retrieves all the hashes assigned to blocks at certain
|
||||
// heights, both canonical and reorged forks included.
|
||||
// This method considers both limits to be _inclusive_.
|
||||
func ReadAllHashesInRange(db ethdb.Iteratee, first, last uint64) []*NumberHash {
|
||||
@@ -204,12 +205,11 @@ func WriteHeadBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
|
||||
// ReadHeaderRLP retrieves a block header in its raw RLP database encoding.
|
||||
func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
// Then try to look up the data in leveldb.
|
||||
data, _ := db.Get(headerKey(number, hash))
|
||||
if len(data) > 0 {
|
||||
return data
|
||||
}
|
||||
return nil // Can't find the data anywhere.
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasHeader verifies the existence of a block header corresponding to the hash.
|
||||
@@ -273,12 +273,11 @@ func deleteHeaderWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number
|
||||
|
||||
// ReadBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
|
||||
func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
// Then try to look up the data in leveldb.
|
||||
data, _ := db.Get(blockBodyKey(number, hash))
|
||||
if len(data) > 0 {
|
||||
return data
|
||||
}
|
||||
return nil // Can't find the data anywhere.
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical
|
||||
@@ -348,12 +347,11 @@ func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool {
|
||||
|
||||
// ReadReceiptsRLP retrieves all the transaction receipts belonging to a block in RLP encoding.
|
||||
func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
// Then try to look up the data in leveldb.
|
||||
data, _ := db.Get(blockReceiptsKey(number, hash))
|
||||
if len(data) > 0 {
|
||||
return data
|
||||
}
|
||||
return nil // Can't find the data anywhere.
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRawReceipts retrieves all the transaction receipts belonging to a block.
|
||||
@@ -397,13 +395,19 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, time uint64,
|
||||
return nil
|
||||
}
|
||||
header := ReadHeader(db, hash, number)
|
||||
|
||||
var baseFee *big.Int
|
||||
if header == nil {
|
||||
baseFee = big.NewInt(0)
|
||||
} else {
|
||||
baseFee = header.BaseFee
|
||||
}
|
||||
if err := receipts.DeriveFields(config, hash, number, time, baseFee, body.Transactions); err != nil {
|
||||
// Compute effective blob gas price.
|
||||
var blobGasPrice *big.Int
|
||||
if header != nil && header.ExcessBlobGas != nil {
|
||||
blobGasPrice = eip4844.CalcBlobFee(*header.ExcessBlobGas)
|
||||
}
|
||||
if err := receipts.DeriveFields(config, hash, number, time, baseFee, blobGasPrice, body.Transactions); err != nil {
|
||||
log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestBodyStorage(t *testing.T) {
|
||||
WriteBody(db, hash, 0, body)
|
||||
if entry := ReadBody(db, hash, 0); entry == nil {
|
||||
t.Fatalf("Stored body not found")
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) {
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) {
|
||||
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body)
|
||||
}
|
||||
if entry := ReadBodyRLP(db, hash, 0); entry == nil {
|
||||
@@ -137,7 +137,7 @@ func TestBlockStorage(t *testing.T) {
|
||||
}
|
||||
if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
t.Fatalf("Stored body not found")
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(block.Transactions(), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(block.Transactions(), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
|
||||
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body())
|
||||
}
|
||||
// Delete the block and verify the execution
|
||||
|
||||
@@ -18,41 +18,17 @@ package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/internal/blocktest"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// testHasher is the helper tool for transaction/receipt list hashing.
|
||||
// The original hasher is trie, in order to get rid of import cycle,
|
||||
// use the testing hasher instead.
|
||||
type testHasher struct {
|
||||
hasher hash.Hash
|
||||
}
|
||||
|
||||
func newHasher() *testHasher {
|
||||
return &testHasher{hasher: sha3.NewLegacyKeccak256()}
|
||||
}
|
||||
|
||||
func (h *testHasher) Reset() {
|
||||
h.hasher.Reset()
|
||||
}
|
||||
|
||||
func (h *testHasher) Update(key, val []byte) error {
|
||||
h.hasher.Write(key)
|
||||
h.hasher.Write(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *testHasher) Hash() common.Hash {
|
||||
return common.BytesToHash(h.hasher.Sum(nil))
|
||||
}
|
||||
var newTestHasher = blocktest.NewHasher
|
||||
|
||||
// Tests that positional lookup metadata can be stored and retrieved.
|
||||
func TestLookupStorage(t *testing.T) {
|
||||
@@ -99,7 +75,7 @@ func TestLookupStorage(t *testing.T) {
|
||||
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
|
||||
txs := []*types.Transaction{tx1, tx2, tx3}
|
||||
|
||||
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newHasher())
|
||||
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newTestHasher())
|
||||
|
||||
// Check that no transactions entries are in a pristine database
|
||||
for i, tx := range txs {
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -77,3 +79,68 @@ func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
log.Crit("Failed to delete contract code", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadStateID retrieves the state id with the provided state root.
|
||||
func ReadStateID(db ethdb.KeyValueReader, root common.Hash) *uint64 {
|
||||
data, err := db.Get(stateIDKey(root))
|
||||
if err != nil || len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
number := binary.BigEndian.Uint64(data)
|
||||
return &number
|
||||
}
|
||||
|
||||
// WriteStateID writes the provided state lookup to database.
|
||||
func WriteStateID(db ethdb.KeyValueWriter, root common.Hash, id uint64) {
|
||||
var buff [8]byte
|
||||
binary.BigEndian.PutUint64(buff[:], id)
|
||||
if err := db.Put(stateIDKey(root), buff[:]); err != nil {
|
||||
log.Crit("Failed to store state ID", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteStateID deletes the specified state lookup from the database.
|
||||
func DeleteStateID(db ethdb.KeyValueWriter, root common.Hash) {
|
||||
if err := db.Delete(stateIDKey(root)); err != nil {
|
||||
log.Crit("Failed to delete state ID", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadPersistentStateID retrieves the id of the persistent state from the database.
|
||||
func ReadPersistentStateID(db ethdb.KeyValueReader) uint64 {
|
||||
data, _ := db.Get(persistentStateIDKey)
|
||||
if len(data) != 8 {
|
||||
return 0
|
||||
}
|
||||
return binary.BigEndian.Uint64(data)
|
||||
}
|
||||
|
||||
// WritePersistentStateID stores the id of the persistent state into database.
|
||||
func WritePersistentStateID(db ethdb.KeyValueWriter, number uint64) {
|
||||
if err := db.Put(persistentStateIDKey, encodeBlockNumber(number)); err != nil {
|
||||
log.Crit("Failed to store the persistent state ID", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTrieJournal retrieves the serialized in-memory trie nodes of layers saved at
|
||||
// the last shutdown.
|
||||
func ReadTrieJournal(db ethdb.KeyValueReader) []byte {
|
||||
data, _ := db.Get(trieJournalKey)
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrieJournal stores the serialized in-memory trie nodes of layers to save at
|
||||
// shutdown.
|
||||
func WriteTrieJournal(db ethdb.KeyValueWriter, journal []byte) {
|
||||
if err := db.Put(trieJournalKey, journal); err != nil {
|
||||
log.Crit("Failed to store tries journal", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrieJournal deletes the serialized in-memory trie nodes of layers saved at
|
||||
// the last shutdown.
|
||||
func DeleteTrieJournal(db ethdb.KeyValueWriter) {
|
||||
if err := db.Delete(trieJournalKey); err != nil {
|
||||
log.Crit("Failed to remove tries journal", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,21 +56,23 @@ const HashScheme = "hashScheme"
|
||||
// on extra state diffs to survive deep reorg.
|
||||
const PathScheme = "pathScheme"
|
||||
|
||||
// nodeHasher used to derive the hash of trie node.
|
||||
type nodeHasher struct{ sha crypto.KeccakState }
|
||||
// hasher is used to compute the sha256 hash of the provided data.
|
||||
type hasher struct{ sha crypto.KeccakState }
|
||||
|
||||
var hasherPool = sync.Pool{
|
||||
New: func() interface{} { return &nodeHasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} },
|
||||
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} },
|
||||
}
|
||||
|
||||
func newNodeHasher() *nodeHasher { return hasherPool.Get().(*nodeHasher) }
|
||||
func returnHasherToPool(h *nodeHasher) { hasherPool.Put(h) }
|
||||
func newHasher() *hasher {
|
||||
return hasherPool.Get().(*hasher)
|
||||
}
|
||||
|
||||
func (h *nodeHasher) hashData(data []byte) (n common.Hash) {
|
||||
h.sha.Reset()
|
||||
h.sha.Write(data)
|
||||
h.sha.Read(n[:])
|
||||
return n
|
||||
func (h *hasher) hash(data []byte) common.Hash {
|
||||
return crypto.HashData(h.sha, data)
|
||||
}
|
||||
|
||||
func (h *hasher) release() {
|
||||
hasherPool.Put(h)
|
||||
}
|
||||
|
||||
// ReadAccountTrieNode retrieves the account trie node and the associated node
|
||||
@@ -80,9 +82,9 @@ func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.H
|
||||
if err != nil {
|
||||
return nil, common.Hash{}
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return data, hasher.hashData(data)
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
return data, h.hash(data)
|
||||
}
|
||||
|
||||
// HasAccountTrieNode checks the account trie node presence with the specified
|
||||
@@ -92,9 +94,9 @@ func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return hasher.hashData(data) == hash
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
return h.hash(data) == hash
|
||||
}
|
||||
|
||||
// WriteAccountTrieNode writes the provided account trie node into database.
|
||||
@@ -118,9 +120,9 @@ func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path
|
||||
if err != nil {
|
||||
return nil, common.Hash{}
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return data, hasher.hashData(data)
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
return data, h.hash(data)
|
||||
}
|
||||
|
||||
// HasStorageTrieNode checks the storage trie node presence with the provided
|
||||
@@ -130,9 +132,9 @@ func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path [
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return hasher.hashData(data) == hash
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
return h.hash(data) == hash
|
||||
}
|
||||
|
||||
// WriteStorageTrieNode writes the provided storage trie node into database.
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestChainIterator(t *testing.T) {
|
||||
var block *types.Block
|
||||
var txs []*types.Transaction
|
||||
to := common.BytesToAddress([]byte{0x11})
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) // Empty genesis block
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) // Empty genesis block
|
||||
WriteBlock(chainDb, block)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
for i := uint64(1); i <= 10; i++ {
|
||||
@@ -70,7 +70,7 @@ func TestChainIterator(t *testing.T) {
|
||||
})
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher())
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher())
|
||||
WriteBlock(chainDb, block)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func TestIndexTransactions(t *testing.T) {
|
||||
to := common.BytesToAddress([]byte{0x11})
|
||||
|
||||
// Write empty genesis block
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher())
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher())
|
||||
WriteBlock(chainDb, block)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
|
||||
@@ -148,7 +148,7 @@ func TestIndexTransactions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher())
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher())
|
||||
WriteBlock(chainDb, block)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
}
|
||||
|
||||
@@ -83,13 +83,13 @@ func (db *nofreezedb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, e
|
||||
}
|
||||
|
||||
// TruncateHead returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) TruncateHead(items uint64) error {
|
||||
return errNotSupported
|
||||
func (db *nofreezedb) TruncateHead(items uint64) (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
// TruncateTail returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) TruncateTail(items uint64) error {
|
||||
return errNotSupported
|
||||
func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
// Sync returns an error as we don't have a backing chain freezer.
|
||||
|
||||
@@ -48,6 +48,9 @@ var (
|
||||
// headBlockKey tracks the latest known full block's hash.
|
||||
headBlockKey = []byte("LastBlock")
|
||||
|
||||
// persistentStateIDKey tracks the id of latest stored state(for path-based only).
|
||||
persistentStateIDKey = []byte("LastStateID")
|
||||
|
||||
// snapshotRootKey tracks the hash of the last snapshot.
|
||||
snapshotRootKey = []byte("SnapshotRoot")
|
||||
|
||||
@@ -57,6 +60,9 @@ var (
|
||||
// snapshotGeneratorKey tracks the snapshot generation marker across restarts.
|
||||
snapshotGeneratorKey = []byte("SnapshotGenerator")
|
||||
|
||||
// trieJournalKey tracks the in-memory trie node layers across restarts.
|
||||
trieJournalKey = []byte("TrieJournal")
|
||||
|
||||
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
||||
txIndexTailKey = []byte("TransactionIndexTail")
|
||||
|
||||
@@ -93,6 +99,7 @@ var (
|
||||
// Path-based storage scheme of merkle patricia trie.
|
||||
trieNodeAccountPrefix = []byte("A") // trieNodeAccountPrefix + hexPath -> trie node
|
||||
trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node
|
||||
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id
|
||||
|
||||
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||
@@ -224,6 +231,11 @@ func upgradeConfigKey(hash common.Hash) []byte {
|
||||
return append(upgradeConfigPrefix, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// stateIDKey = stateIDPrefix + root (32 bytes)
|
||||
func stateIDKey(root common.Hash) []byte {
|
||||
return append(stateIDPrefix, root.Bytes()...)
|
||||
}
|
||||
|
||||
// accountTrieNodeKey = trieNodeAccountPrefix + nodePath.
|
||||
func accountTrieNodeKey(path []byte) []byte {
|
||||
return append(trieNodeAccountPrefix, path...)
|
||||
|
||||
+2
-2
@@ -107,13 +107,13 @@ func (t *table) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err e
|
||||
|
||||
// TruncateHead is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) TruncateHead(items uint64) error {
|
||||
func (t *table) TruncateHead(items uint64) (uint64, error) {
|
||||
return t.db.TruncateHead(items)
|
||||
}
|
||||
|
||||
// TruncateTail is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) TruncateTail(items uint64) error {
|
||||
func (t *table) TruncateTail(items uint64) (uint64, error) {
|
||||
return t.db.TruncateTail(items)
|
||||
}
|
||||
|
||||
|
||||
+18
-12
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/ava-labs/subnet-evm/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
@@ -53,16 +54,16 @@ type Database interface {
|
||||
OpenTrie(root common.Hash) (Trie, error)
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error)
|
||||
OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error)
|
||||
|
||||
// CopyTrie returns an independent copy of the given trie.
|
||||
CopyTrie(Trie) Trie
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
|
||||
ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error)
|
||||
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
|
||||
ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error)
|
||||
|
||||
// DiskDB returns the underlying key-value disk database.
|
||||
DiskDB() ethdb.KeyValueStore
|
||||
@@ -103,6 +104,10 @@ type Trie interface {
|
||||
// in the trie with provided address.
|
||||
UpdateAccount(address common.Address, account *types.StateAccount) error
|
||||
|
||||
// UpdateContractCode abstracts code write to the trie. It is expected
|
||||
// to be moved to the stateWriter interface when the latter is ready.
|
||||
UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error
|
||||
|
||||
// DeleteStorage removes any existing value for key from the trie. If a node
|
||||
// was not found in the database, a trie.MissingNodeError is returned.
|
||||
DeleteStorage(addr common.Address, key []byte) error
|
||||
@@ -120,11 +125,12 @@ type Trie interface {
|
||||
// The returned nodeset can be nil if the trie is clean(nothing to commit).
|
||||
// Once the trie is committed, it's not usable anymore. A new trie must
|
||||
// be created with new root and updated trie database for following usage
|
||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
|
||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
|
||||
|
||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
||||
// starts at the key after the given start key.
|
||||
NodeIterator(startKey []byte) trie.NodeIterator
|
||||
// starts at the key after the given start key. And error will be returned
|
||||
// if fails to create node iterator.
|
||||
NodeIterator(startKey []byte) (trie.NodeIterator, error)
|
||||
|
||||
// Prove constructs a Merkle proof for key. The result contains all encoded nodes
|
||||
// on the path to the value at key. The value itself is also included in the last
|
||||
@@ -133,7 +139,7 @@ type Trie interface {
|
||||
// If the trie does not contain a value for key, the returned proof contains all
|
||||
// nodes of the longest existing prefix of the key (at least the root), ending
|
||||
// with the node that proves the absence of the key.
|
||||
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
|
||||
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
||||
}
|
||||
|
||||
// NewDatabase creates a backing store for state. The returned database is safe for
|
||||
@@ -182,8 +188,8 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) {
|
||||
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, addrHash, root), db.triedb)
|
||||
func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error) {
|
||||
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -201,7 +207,7 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
|
||||
}
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
|
||||
func (db *cachingDB) ContractCode(address common.Address, codeHash common.Hash) ([]byte, error) {
|
||||
code, _ := db.codeCache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
return code, nil
|
||||
@@ -216,11 +222,11 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error
|
||||
}
|
||||
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
|
||||
func (db *cachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
||||
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
|
||||
return cached, nil
|
||||
}
|
||||
code, err := db.ContractCode(addrHash, codeHash)
|
||||
code, err := db.ContractCode(addr, codeHash)
|
||||
return len(code), err
|
||||
}
|
||||
|
||||
|
||||
+14
-5
@@ -150,7 +150,11 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||
log.Info("Trie dumping started", "root", s.trie.Hash())
|
||||
c.OnRoot(s.trie.Hash())
|
||||
|
||||
it := trie.NewIterator(s.trie.NodeIterator(conf.Start))
|
||||
trieIt, err := s.trie.NodeIterator(conf.Start)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
it := trie.NewIterator(trieIt)
|
||||
for it.Next() {
|
||||
var data types.StateAccount
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
@@ -177,18 +181,23 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||
} else {
|
||||
address = &addr
|
||||
}
|
||||
obj := newObject(s, addr, data)
|
||||
obj := newObject(s, addr, &data)
|
||||
if !conf.SkipCode {
|
||||
account.Code = obj.Code(s.db)
|
||||
account.Code = obj.Code()
|
||||
}
|
||||
if !conf.SkipStorage {
|
||||
account.Storage = make(map[common.Hash]string)
|
||||
tr, err := obj.getTrie(s.db)
|
||||
tr, err := obj.getTrie()
|
||||
if err != nil {
|
||||
log.Error("Failed to load storage trie", "err", err)
|
||||
continue
|
||||
}
|
||||
storageIt := trie.NewIterator(tr.NodeIterator(nil))
|
||||
trieIt, err := tr.NodeIterator(nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to create trie iterator", "err", err)
|
||||
continue
|
||||
}
|
||||
storageIt := trie.NewIterator(trieIt)
|
||||
for storageIt.Next() {
|
||||
_, content, _, err := rlp.Split(storageIt.Value)
|
||||
if err != nil {
|
||||
|
||||
+22
-6
@@ -28,6 +28,7 @@ package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
@@ -37,7 +38,8 @@ import (
|
||||
)
|
||||
|
||||
// nodeIterator is an iterator to traverse the entire state trie post-order,
|
||||
// including all of the contract code and contract state tries.
|
||||
// including all of the contract code and contract state tries. Preimage is
|
||||
// required in order to resolve the contract address.
|
||||
type nodeIterator struct {
|
||||
state *StateDB // State being iterated
|
||||
|
||||
@@ -84,8 +86,12 @@ func (it *nodeIterator) step() error {
|
||||
return nil
|
||||
}
|
||||
// Initialize the iterator if we've just started
|
||||
var err error
|
||||
if it.stateIt == nil {
|
||||
it.stateIt = it.state.trie.NodeIterator(nil)
|
||||
it.stateIt, err = it.state.trie.NodeIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// If we had data nodes previously, we surely have at least state nodes
|
||||
if it.dataIt != nil {
|
||||
@@ -119,18 +125,28 @@ func (it *nodeIterator) step() error {
|
||||
if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob()), &account); err != nil {
|
||||
return err
|
||||
}
|
||||
dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, common.BytesToHash(it.stateIt.LeafKey()), account.Root)
|
||||
// Lookup the preimage of account hash
|
||||
preimage := it.state.trie.GetKey(it.stateIt.LeafKey())
|
||||
if preimage == nil {
|
||||
return errors.New("account address is not available")
|
||||
}
|
||||
address := common.BytesToAddress(preimage)
|
||||
|
||||
// Traverse the storage slots belong to the account
|
||||
dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it.dataIt, err = dataTrie.NodeIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it.dataIt = dataTrie.NodeIterator(nil)
|
||||
if !it.dataIt.Next(true) {
|
||||
it.dataIt = nil
|
||||
}
|
||||
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||
it.codeHash = common.BytesToHash(account.CodeHash)
|
||||
addrHash := common.BytesToHash(it.stateIt.LeafKey())
|
||||
it.code, err = it.state.db.ContractCode(addrHash, common.BytesToHash(account.CodeHash))
|
||||
it.code, err = it.state.db.ContractCode(address, common.BytesToHash(account.CodeHash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("code %x: %v", account.CodeHash, err)
|
||||
}
|
||||
|
||||
+25
-6
@@ -100,12 +100,19 @@ type (
|
||||
account *common.Address
|
||||
}
|
||||
resetObjectChange struct {
|
||||
account *common.Address
|
||||
prev *stateObject
|
||||
prevdestruct bool
|
||||
prevAccount []byte
|
||||
prevStorage map[common.Hash][]byte
|
||||
|
||||
prevAccountOriginExist bool
|
||||
prevAccountOrigin []byte
|
||||
prevStorageOrigin map[common.Hash][]byte
|
||||
}
|
||||
suicideChange struct {
|
||||
selfDestructChange struct {
|
||||
account *common.Address
|
||||
prev bool // whether account had already suicided
|
||||
prev bool // whether account had already self-destructed
|
||||
prevbalance *big.Int
|
||||
}
|
||||
|
||||
@@ -169,21 +176,33 @@ func (ch resetObjectChange) revert(s *StateDB) {
|
||||
if !ch.prevdestruct {
|
||||
delete(s.stateObjectsDestruct, ch.prev.address)
|
||||
}
|
||||
if ch.prevAccount != nil {
|
||||
s.accounts[ch.prev.addrHash] = ch.prevAccount
|
||||
}
|
||||
if ch.prevStorage != nil {
|
||||
s.storages[ch.prev.addrHash] = ch.prevStorage
|
||||
}
|
||||
if ch.prevAccountOriginExist {
|
||||
s.accountsOrigin[ch.prev.address] = ch.prevAccountOrigin
|
||||
}
|
||||
if ch.prevStorageOrigin != nil {
|
||||
s.storagesOrigin[ch.prev.address] = ch.prevStorageOrigin
|
||||
}
|
||||
}
|
||||
|
||||
func (ch resetObjectChange) dirtied() *common.Address {
|
||||
return nil
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch suicideChange) revert(s *StateDB) {
|
||||
func (ch selfDestructChange) revert(s *StateDB) {
|
||||
obj := s.getStateObject(*ch.account)
|
||||
if obj != nil {
|
||||
obj.suicided = ch.prev
|
||||
obj.selfDestructed = ch.prev
|
||||
obj.setBalance(ch.prevbalance)
|
||||
}
|
||||
}
|
||||
|
||||
func (ch suicideChange) dirtied() *common.Address {
|
||||
func (ch selfDestructChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
|
||||
@@ -37,4 +37,11 @@ var (
|
||||
storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil)
|
||||
accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil)
|
||||
storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil)
|
||||
|
||||
slotDeletionMaxCount = metrics.NewRegisteredGauge("state/delete/storage/max/slot", nil)
|
||||
slotDeletionMaxSize = metrics.NewRegisteredGauge("state/delete/storage/max/size", nil)
|
||||
slotDeletionTimer = metrics.NewRegisteredResettingTimer("state/delete/storage/timer", nil)
|
||||
slotDeletionCount = metrics.NewRegisteredMeter("state/delete/storage/slot", nil)
|
||||
slotDeletionSize = metrics.NewRegisteredMeter("state/delete/storage/size", nil)
|
||||
slotDeletionSkip = metrics.NewRegisteredGauge("state/delete/storage/skip", nil)
|
||||
)
|
||||
|
||||
+10
-36
@@ -67,7 +67,6 @@ const (
|
||||
// Config includes all the configurations for pruning.
|
||||
type Config struct {
|
||||
Datadir string // The directory of the state database
|
||||
Cachedir string // The directory of state clean cache
|
||||
BloomSize uint64 // The Megabytes of memory allocated to bloom-filter
|
||||
}
|
||||
|
||||
@@ -261,7 +260,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
return err
|
||||
}
|
||||
if stateBloomRoot != (common.Hash{}) {
|
||||
return RecoverPruning(p.config.Datadir, p.db, p.config.Cachedir)
|
||||
return RecoverPruning(p.config.Datadir, p.db)
|
||||
}
|
||||
|
||||
// If the target state root is not specified, return a fatal error.
|
||||
@@ -276,11 +275,6 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
} else {
|
||||
log.Info("Selecting last accepted block root as the pruning target", "root", root)
|
||||
}
|
||||
// Before start the pruning, delete the clean trie cache first.
|
||||
// It's necessary otherwise in the next restart we will hit the
|
||||
// deleted state root in the "clean cache" so that the incomplete
|
||||
// state is picked for usage.
|
||||
deleteCleanTrieCache(p.config.Cachedir)
|
||||
|
||||
// Traverse the target state, re-construct the whole state trie and
|
||||
// commit to the given bloom filter.
|
||||
@@ -310,7 +304,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
// pruning can be resumed. What's more if the bloom filter is constructed, the
|
||||
// pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left
|
||||
// in the disk.
|
||||
func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) error {
|
||||
func RecoverPruning(datadir string, db ethdb.Database) error {
|
||||
stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -328,12 +322,6 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err
|
||||
}
|
||||
log.Info("Loaded state bloom filter", "path", stateBloomPath)
|
||||
|
||||
// Before start the pruning, delete the clean trie cache first.
|
||||
// It's necessary otherwise in the next restart we will hit the
|
||||
// deleted state root in the "clean cache" so that the incomplete
|
||||
// state is picked for usage.
|
||||
deleteCleanTrieCache(trieCachePath)
|
||||
|
||||
// All the state roots of the middle layers should be forcibly pruned,
|
||||
// otherwise the dangling state will be left.
|
||||
if stateBloomRoot != headBlock.Root() {
|
||||
@@ -358,7 +346,10 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accIter := t.NodeIterator(nil)
|
||||
accIter, err := t.NodeIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for accIter.Next(true) {
|
||||
hash := accIter.Hash()
|
||||
|
||||
@@ -379,7 +370,10 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storageIter := storageTrie.NodeIterator(nil)
|
||||
storageIter, err := storageTrie.NodeIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for storageIter.Next(true) {
|
||||
hash := storageIter.Hash()
|
||||
if hash != (common.Hash{}) {
|
||||
@@ -429,23 +423,3 @@ func findBloomFilter(datadir string) (string, common.Hash, error) {
|
||||
}
|
||||
return stateBloomPath, stateBloomRoot, nil
|
||||
}
|
||||
|
||||
const warningLog = `
|
||||
|
||||
WARNING!
|
||||
|
||||
The clean trie cache is not found. Please delete it by yourself after the
|
||||
pruning. Remember don't start the Subnet-EVM without deleting the clean trie cache
|
||||
otherwise the entire database may be damaged!
|
||||
|
||||
Check the configuration option "offline-pruning-enabled" for more details.
|
||||
`
|
||||
|
||||
func deleteCleanTrieCache(path string) {
|
||||
if !common.FileExist(path) {
|
||||
log.Warn(warningLog)
|
||||
return
|
||||
}
|
||||
os.RemoveAll(path)
|
||||
log.Info("Deleted trie clean cache", "path", path)
|
||||
}
|
||||
|
||||
@@ -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 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 snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Account is a modified version of a state.Account, where the root is replaced
|
||||
// with a byte slice. This format can be used to represent full-consensus format
|
||||
// or slim-snapshot format which replaces the empty root and code hash as nil
|
||||
// byte slice.
|
||||
type Account struct {
|
||||
Nonce uint64
|
||||
Balance *big.Int
|
||||
Root []byte
|
||||
CodeHash []byte
|
||||
}
|
||||
|
||||
// SlimAccount converts a state.Account content into a slim snapshot account
|
||||
func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) Account {
|
||||
slim := Account{
|
||||
Nonce: nonce,
|
||||
Balance: balance,
|
||||
}
|
||||
if root != types.EmptyRootHash {
|
||||
slim.Root = root[:]
|
||||
}
|
||||
if !bytes.Equal(codehash, types.EmptyCodeHash[:]) {
|
||||
slim.CodeHash = codehash
|
||||
}
|
||||
return slim
|
||||
}
|
||||
|
||||
// SlimAccountRLP converts a state.Account content into a slim snapshot
|
||||
// version RLP encoded.
|
||||
func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte {
|
||||
data, err := rlp.EncodeToBytes(SlimAccount(nonce, balance, root, codehash))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// FullAccount decodes the data on the 'slim RLP' format and return
|
||||
// the consensus format account.
|
||||
func FullAccount(data []byte) (Account, error) {
|
||||
var account Account
|
||||
if err := rlp.DecodeBytes(data, &account); err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
if len(account.Root) == 0 {
|
||||
account.Root = types.EmptyRootHash[:]
|
||||
}
|
||||
if len(account.CodeHash) == 0 {
|
||||
account.CodeHash = types.EmptyCodeHash[:]
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// FullAccountRLP converts data on the 'slim RLP' format into the full RLP-format.
|
||||
func FullAccountRLP(data []byte) ([]byte, error) {
|
||||
account, err := FullAccount(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rlp.EncodeToBytes(account)
|
||||
}
|
||||
@@ -27,7 +27,6 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -311,7 +310,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou
|
||||
fullData []byte
|
||||
)
|
||||
if leafCallback == nil {
|
||||
fullData, err = FullAccountRLP(it.(AccountIterator).Account())
|
||||
fullData, err = types.FullAccountRLP(it.(AccountIterator).Account())
|
||||
if err != nil {
|
||||
return stop(err)
|
||||
}
|
||||
@@ -323,7 +322,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou
|
||||
return stop(err)
|
||||
}
|
||||
// Fetch the next account and process it concurrently
|
||||
account, err := FullAccount(it.(AccountIterator).Account())
|
||||
account, err := types.FullAccount(it.(AccountIterator).Account())
|
||||
if err != nil {
|
||||
return stop(err)
|
||||
}
|
||||
@@ -333,7 +332,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou
|
||||
results <- err
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(account.Root, subroot.Bytes()) {
|
||||
if account.Root != subroot {
|
||||
results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -31,14 +31,15 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
bloomfilter "github.com/holiman/bloomfilter/v2"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -289,7 +290,7 @@ func (dl *diffLayer) Stale() bool {
|
||||
|
||||
// Account directly retrieves the account associated with a particular hash in
|
||||
// the snapshot slim data format.
|
||||
func (dl *diffLayer) Account(hash common.Hash) (*Account, error) {
|
||||
func (dl *diffLayer) Account(hash common.Hash) (*types.SlimAccount, error) {
|
||||
data, err := dl.AccountRLP(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -297,7 +298,7 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) {
|
||||
if len(data) == 0 { // can be both nil and []byte{}
|
||||
return nil, nil
|
||||
}
|
||||
account := new(Account)
|
||||
account := new(types.SlimAccount)
|
||||
if err := rlp.DecodeBytes(data, account); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -309,8 +310,8 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) {
|
||||
//
|
||||
// Note the returned account is not a copy, please don't modify it.
|
||||
func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) {
|
||||
dl.lock.RLock()
|
||||
// Check staleness before reaching further.
|
||||
dl.lock.RLock()
|
||||
if dl.Stale() {
|
||||
dl.lock.RUnlock()
|
||||
return nil, ErrSnapshotStale
|
||||
@@ -541,7 +542,7 @@ func (dl *diffLayer) AccountList() []common.Hash {
|
||||
dl.accountList = append(dl.accountList, hash)
|
||||
}
|
||||
}
|
||||
sort.Sort(hashes(dl.accountList))
|
||||
slices.SortFunc(dl.accountList, common.Hash.Cmp)
|
||||
dl.memory += uint64(len(dl.accountList) * common.HashLength)
|
||||
return dl.accountList
|
||||
}
|
||||
@@ -579,7 +580,7 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) ([]common.Hash, bool)
|
||||
for k := range storageMap {
|
||||
storageList = append(storageList, k)
|
||||
}
|
||||
sort.Sort(hashes(storageList))
|
||||
slices.SortFunc(storageList, common.Hash.Cmp)
|
||||
dl.storageList[accountHash] = storageList
|
||||
dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
|
||||
return storageList, destructed
|
||||
|
||||
@@ -246,7 +246,7 @@ func TestInsertAndMerge(t *testing.T) {
|
||||
func emptyLayer() *diskLayer {
|
||||
return &diskLayer{
|
||||
diskdb: memorydb.New(),
|
||||
cache: utils.NewMeteredCache(500*1024, "", "", 0),
|
||||
cache: utils.NewMeteredCache(500*1024, "", 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/trie"
|
||||
"github.com/ava-labs/subnet-evm/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -88,7 +89,7 @@ func (dl *diskLayer) Stale() bool {
|
||||
|
||||
// Account directly retrieves the account associated with a particular hash in
|
||||
// the snapshot slim data format.
|
||||
func (dl *diskLayer) Account(hash common.Hash) (*Account, error) {
|
||||
func (dl *diskLayer) Account(hash common.Hash) (*types.SlimAccount, error) {
|
||||
data, err := dl.AccountRLP(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -96,7 +97,7 @@ func (dl *diskLayer) Account(hash common.Hash) (*Account, error) {
|
||||
if len(data) == 0 { // can be both nil and []byte{}
|
||||
return nil, nil
|
||||
}
|
||||
account := new(Account)
|
||||
account := new(types.SlimAccount)
|
||||
if err := rlp.DecodeBytes(data, account); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
@@ -287,7 +286,15 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that
|
||||
accMarker = dl.genMarker[:common.HashLength]
|
||||
}
|
||||
accIt := trie.NewIterator(accTrie.NodeIterator(accMarker))
|
||||
nodeIt, err := accTrie.NodeIterator(accMarker)
|
||||
if err != nil {
|
||||
log.Error("Generator failed to create account iterator", "root", dl)
|
||||
abort := <-dl.genAbort
|
||||
dl.genStats = stats
|
||||
close(abort)
|
||||
return
|
||||
}
|
||||
accIt := trie.NewIterator(nodeIt)
|
||||
batch := dl.diskdb.NewBatch()
|
||||
|
||||
// Iterate from the previous marker and continue generating the state snapshot
|
||||
@@ -296,16 +303,11 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
// Retrieve the current account and flatten it into the internal format
|
||||
accountHash := common.BytesToHash(accIt.Key)
|
||||
|
||||
var acc struct {
|
||||
Nonce uint64
|
||||
Balance *big.Int
|
||||
Root common.Hash
|
||||
CodeHash []byte
|
||||
}
|
||||
var acc types.StateAccount
|
||||
if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil {
|
||||
log.Crit("Invalid account encountered during snapshot creation", "err", err)
|
||||
}
|
||||
data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash)
|
||||
data := types.SlimAccountRLP(acc)
|
||||
|
||||
// If the account is not yet in-progress, write it out
|
||||
if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) {
|
||||
@@ -339,7 +341,15 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength {
|
||||
storeMarker = dl.genMarker[common.HashLength:]
|
||||
}
|
||||
storeIt := trie.NewIterator(storeTrie.NodeIterator(storeMarker))
|
||||
nodeIt, err := storeTrie.NodeIterator(storeMarker)
|
||||
if err != nil {
|
||||
log.Error("Generator failed to create storage iterator", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err)
|
||||
abort := <-dl.genAbort
|
||||
dl.genStats = stats
|
||||
close(abort)
|
||||
return
|
||||
}
|
||||
storeIt := trie.NewIterator(nodeIt)
|
||||
for storeIt.Next() {
|
||||
rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value)
|
||||
stats.storage += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value))
|
||||
@@ -399,5 +409,5 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
}
|
||||
|
||||
func newMeteredSnapshotCache(size int) *utils.MeteredCache {
|
||||
return utils.NewMeteredCache(size, "", snapshotCacheNamespace, snapshotCacheStatsUpdateFrequency)
|
||||
return utils.NewMeteredCache(size, snapshotCacheNamespace, snapshotCacheStatsUpdateFrequency)
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ func TestGeneration(t *testing.T) {
|
||||
var helper = newHelper()
|
||||
stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, false)
|
||||
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
@@ -97,16 +97,16 @@ func TestGenerateExistentState(t *testing.T) {
|
||||
var helper = newHelper()
|
||||
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
root, snap := helper.CommitAndGenerate()
|
||||
@@ -170,18 +170,17 @@ func newHelper() *testHelper {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testHelper) addTrieAccount(acckey string, acc *Account) {
|
||||
func (t *testHelper) addTrieAccount(acckey string, acc *types.StateAccount) {
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
t.accTrie.MustUpdate([]byte(acckey), val)
|
||||
}
|
||||
|
||||
func (t *testHelper) addSnapAccount(acckey string, acc *Account) {
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
func (t *testHelper) addSnapAccount(acckey string, acc *types.StateAccount) {
|
||||
key := hashData([]byte(acckey))
|
||||
rawdb.WriteAccountSnapshot(t.diskdb, key, val)
|
||||
rawdb.WriteAccountSnapshot(t.diskdb, key, types.SlimAccountRLP(*acc))
|
||||
}
|
||||
|
||||
func (t *testHelper) addAccount(acckey string, acc *Account) {
|
||||
func (t *testHelper) addAccount(acckey string, acc *types.StateAccount) {
|
||||
t.addTrieAccount(acckey, acc)
|
||||
t.addSnapAccount(acckey, acc)
|
||||
}
|
||||
@@ -193,28 +192,28 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) []byte {
|
||||
func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) common.Hash {
|
||||
id := trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash)
|
||||
stTrie, _ := trie.NewStateTrie(id, t.triedb)
|
||||
for i, k := range keys {
|
||||
stTrie.MustUpdate([]byte(k), []byte(vals[i]))
|
||||
}
|
||||
if !commit {
|
||||
return stTrie.Hash().Bytes()
|
||||
return stTrie.Hash()
|
||||
}
|
||||
root, nodes := stTrie.Commit(false)
|
||||
root, nodes, _ := stTrie.Commit(false)
|
||||
if nodes != nil {
|
||||
t.nodes.Merge(nodes)
|
||||
}
|
||||
return root.Bytes()
|
||||
return root
|
||||
}
|
||||
|
||||
func (t *testHelper) Commit() common.Hash {
|
||||
root, nodes := t.accTrie.Commit(true)
|
||||
root, nodes, _ := t.accTrie.Commit(true)
|
||||
if nodes != nil {
|
||||
t.nodes.Merge(nodes)
|
||||
}
|
||||
t.triedb.Update(root, types.EmptyRootHash, t.nodes)
|
||||
t.triedb.Update(root, types.EmptyRootHash, 0, t.nodes, nil)
|
||||
t.triedb.Commit(root, false)
|
||||
return root
|
||||
}
|
||||
@@ -247,28 +246,28 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
|
||||
helper := newHelper()
|
||||
|
||||
// Account one, empty root but non-empty database
|
||||
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
|
||||
// Account two, non empty root but empty database
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
// Miss slots
|
||||
{
|
||||
// Account three, non empty root but misses slots in the beginning
|
||||
helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"})
|
||||
|
||||
// Account four, non empty root but misses slots in the middle
|
||||
helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-4", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"})
|
||||
|
||||
// Account five, non empty root but misses slots in the end
|
||||
helper.makeStorageTrie(hashData([]byte("acc-5")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-5", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-5", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"})
|
||||
}
|
||||
|
||||
@@ -276,22 +275,22 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
|
||||
{
|
||||
// Account six, non empty root but wrong slots in the beginning
|
||||
helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-6", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"})
|
||||
|
||||
// Account seven, non empty root but wrong slots in the middle
|
||||
helper.makeStorageTrie(hashData([]byte("acc-7")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-7", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-7", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"})
|
||||
|
||||
// Account eight, non empty root but wrong slots in the end
|
||||
helper.makeStorageTrie(hashData([]byte("acc-8")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-8", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-8", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"})
|
||||
|
||||
// Account 9, non empty root but rotated slots
|
||||
helper.makeStorageTrie(hashData([]byte("acc-9")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-9", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-9", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"})
|
||||
}
|
||||
|
||||
@@ -299,17 +298,17 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
|
||||
{
|
||||
// Account 10, non empty root but extra slots in the beginning
|
||||
helper.makeStorageTrie(hashData([]byte("acc-10")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-10", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-10", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"})
|
||||
|
||||
// Account 11, non empty root but extra slots in the middle
|
||||
helper.makeStorageTrie(hashData([]byte("acc-11")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-11", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-11", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"})
|
||||
|
||||
// Account 12, non empty root but extra slots in the end
|
||||
helper.makeStorageTrie(hashData([]byte("acc-12")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-12", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-12", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"})
|
||||
}
|
||||
|
||||
@@ -349,25 +348,25 @@ func TestGenerateExistentStateWithWrongAccounts(t *testing.T) {
|
||||
|
||||
// Missing accounts, only in the trie
|
||||
{
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Beginning
|
||||
helper.addTrieAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Middle
|
||||
helper.addTrieAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // End
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Beginning
|
||||
helper.addTrieAccount("acc-4", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Middle
|
||||
helper.addTrieAccount("acc-6", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // End
|
||||
}
|
||||
|
||||
// Wrong accounts
|
||||
{
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")})
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")})
|
||||
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addSnapAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
}
|
||||
|
||||
// Extra accounts, only in the snap
|
||||
{
|
||||
helper.addSnapAccount("acc-0", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // before the beginning
|
||||
helper.addSnapAccount("acc-5", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: common.Hex2Bytes("0x1234")}) // Middle
|
||||
helper.addSnapAccount("acc-7", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // after the end
|
||||
helper.addSnapAccount("acc-0", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // before the beginning
|
||||
helper.addSnapAccount("acc-5", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: common.Hex2Bytes("0x1234")}) // Middle
|
||||
helper.addSnapAccount("acc-7", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // after the end
|
||||
}
|
||||
|
||||
root, snap := helper.CommitAndGenerate()
|
||||
@@ -396,9 +395,9 @@ func TestGenerateCorruptAccountTrie(t *testing.T) {
|
||||
// without any storage slots to keep the test smaller.
|
||||
helper := newHelper()
|
||||
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
|
||||
|
||||
root := helper.Commit() // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
|
||||
|
||||
@@ -430,15 +429,16 @@ func TestGenerateMissingStorageTrie(t *testing.T) {
|
||||
// two of which also has the same 3-slot storage trie attached.
|
||||
helper := newHelper()
|
||||
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
|
||||
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
|
||||
|
||||
root := helper.Commit()
|
||||
|
||||
// Delete a storage trie root and ensure the generator chokes
|
||||
helper.diskdb.Delete(stRoot) // We can only corrupt the disk database, so flush the tries out
|
||||
helper.diskdb.Delete(stRoot.Bytes())
|
||||
|
||||
snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil)
|
||||
select {
|
||||
@@ -463,11 +463,12 @@ func TestGenerateCorruptStorageTrie(t *testing.T) {
|
||||
// two of which also has the same 3-slot storage trie attached.
|
||||
helper := newHelper()
|
||||
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
|
||||
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
|
||||
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
|
||||
|
||||
root := helper.Commit()
|
||||
|
||||
// Delete a storage trie leaf and ensure the generator chokes
|
||||
@@ -498,7 +499,7 @@ func TestGenerateWithExtraAccounts(t *testing.T) {
|
||||
[]string{"val-1", "val-2", "val-3", "val-4", "val-5"},
|
||||
true,
|
||||
)
|
||||
acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
acc := &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
|
||||
|
||||
@@ -518,7 +519,7 @@ func TestGenerateWithExtraAccounts(t *testing.T) {
|
||||
[]string{"val-1", "val-2", "val-3", "val-4", "val-5"},
|
||||
true,
|
||||
)
|
||||
acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
acc := &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
key := hashData([]byte("acc-2"))
|
||||
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
|
||||
@@ -569,7 +570,7 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) {
|
||||
[]string{"val-1", "val-2", "val-3"},
|
||||
true,
|
||||
)
|
||||
acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
acc := &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
|
||||
|
||||
@@ -583,7 +584,7 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) {
|
||||
{
|
||||
// 100 accounts exist only in snapshot
|
||||
for i := 0; i < 1000; i++ {
|
||||
acc := &Account{Balance: big.NewInt(int64(i)), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
acc := &types.StateAccount{Balance: big.NewInt(int64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
key := hashData([]byte(fmt.Sprintf("acc-%d", i)))
|
||||
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
|
||||
@@ -619,7 +620,7 @@ func TestGenerateWithExtraBeforeAndAfter(t *testing.T) {
|
||||
}
|
||||
helper := newHelper()
|
||||
{
|
||||
acc := &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
acc := &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
helper.accTrie.MustUpdate(common.HexToHash("0x03").Bytes(), val)
|
||||
helper.accTrie.MustUpdate(common.HexToHash("0x07").Bytes(), val)
|
||||
@@ -655,7 +656,7 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) {
|
||||
}
|
||||
helper := newHelper()
|
||||
{
|
||||
acc := &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
acc := &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
helper.accTrie.MustUpdate(common.HexToHash("0x03").Bytes(), val)
|
||||
|
||||
@@ -692,7 +693,7 @@ func TestGenerateFromEmptySnap(t *testing.T) {
|
||||
for i := 0; i < 400; i++ {
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte(fmt.Sprintf("acc-%d", i))), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addTrieAccount(fmt.Sprintf("acc-%d", i),
|
||||
&Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
&types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
}
|
||||
root, snap := helper.CommitAndGenerate()
|
||||
t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4
|
||||
@@ -728,7 +729,7 @@ func TestGenerateWithIncompleteStorage(t *testing.T) {
|
||||
for i := 0; i < 8; i++ {
|
||||
accKey := fmt.Sprintf("acc-%d", i)
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte(accKey)), stKeys, stVals, true)
|
||||
helper.addAccount(accKey, &Account{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount(accKey, &types.StateAccount{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
var moddedKeys []string
|
||||
var moddedVals []string
|
||||
for ii := 0; ii < 8; ii++ {
|
||||
@@ -819,12 +820,12 @@ func populateDangling(disk ethdb.KeyValueStore) {
|
||||
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
|
||||
var helper = newHelper()
|
||||
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
|
||||
@@ -855,11 +856,11 @@ func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
|
||||
var helper = newHelper()
|
||||
|
||||
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
|
||||
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
|
||||
|
||||
populateDangling(helper.diskdb)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// weightedIterator is a iterator with an assigned weight. It is used to prioritise
|
||||
@@ -42,32 +43,25 @@ type weightedIterator struct {
|
||||
priority int
|
||||
}
|
||||
|
||||
// weightedIterators is a set of iterators implementing the sort.Interface.
|
||||
type weightedIterators []*weightedIterator
|
||||
|
||||
// Len implements sort.Interface, returning the number of active iterators.
|
||||
func (its weightedIterators) Len() int { return len(its) }
|
||||
|
||||
// Less implements sort.Interface, returning which of two iterators in the stack
|
||||
// is before the other.
|
||||
func (its weightedIterators) Less(i, j int) bool {
|
||||
func (it *weightedIterator) Cmp(other *weightedIterator) int {
|
||||
// Order the iterators primarily by the account hashes
|
||||
hashI := its[i].it.Hash()
|
||||
hashJ := its[j].it.Hash()
|
||||
hashI := it.it.Hash()
|
||||
hashJ := other.it.Hash()
|
||||
|
||||
switch bytes.Compare(hashI[:], hashJ[:]) {
|
||||
case -1:
|
||||
return true
|
||||
return -1
|
||||
case 1:
|
||||
return false
|
||||
return 1
|
||||
}
|
||||
// Same account/storage-slot in multiple layers, split by priority
|
||||
return its[i].priority < its[j].priority
|
||||
}
|
||||
|
||||
// Swap implements sort.Interface, swapping two entries in the iterator stack.
|
||||
func (its weightedIterators) Swap(i, j int) {
|
||||
its[i], its[j] = its[j], its[i]
|
||||
if it.priority < other.priority {
|
||||
return -1
|
||||
}
|
||||
if it.priority > other.priority {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// fastIterator is a more optimized multi-layer iterator which maintains a
|
||||
@@ -79,7 +73,7 @@ type fastIterator struct {
|
||||
curAccount []byte
|
||||
curSlot []byte
|
||||
|
||||
iterators weightedIterators
|
||||
iterators []*weightedIterator
|
||||
initiated bool
|
||||
account bool
|
||||
fail error
|
||||
@@ -176,7 +170,7 @@ func (fi *fastIterator) init() {
|
||||
}
|
||||
}
|
||||
// Re-sort the entire list
|
||||
sort.Sort(fi.iterators)
|
||||
slices.SortFunc(fi.iterators, func(a, b *weightedIterator) int { return a.Cmp(b) })
|
||||
fi.initiated = false
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
|
||||
// is present in the database (or crashed mid-update).
|
||||
baseBlockHash := rawdb.ReadSnapshotBlockHash(diskdb)
|
||||
if baseBlockHash == (common.Hash{}) {
|
||||
return nil, false, fmt.Errorf("missing or corrupted snapshot, no snapshot block hash")
|
||||
return nil, false, errors.New("missing or corrupted snapshot, no snapshot block hash")
|
||||
}
|
||||
if baseBlockHash != blockHash {
|
||||
return nil, false, fmt.Errorf("block hash stored on disk (%#x) does not match last accepted (%#x)", baseBlockHash, blockHash)
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/metrics"
|
||||
"github.com/ava-labs/subnet-evm/trie"
|
||||
"github.com/ava-labs/subnet-evm/utils"
|
||||
@@ -124,7 +125,7 @@ type Snapshot interface {
|
||||
|
||||
// Account directly retrieves the account associated with a particular hash in
|
||||
// the snapshot slim data format.
|
||||
Account(hash common.Hash) (*Account, error)
|
||||
Account(hash common.Hash) (*types.SlimAccount, error)
|
||||
|
||||
// AccountRLP directly retrieves the account RLP associated with a particular
|
||||
// hash in the snapshot slim data format.
|
||||
@@ -944,7 +945,7 @@ func NewDiskLayer(diskdb ethdb.KeyValueStore) Snapshot {
|
||||
|
||||
// state sync uses iterators to access data, so this cache is not used.
|
||||
// initializing it out of caution.
|
||||
cache: utils.NewMeteredCache(32*1024, "", "", 0),
|
||||
cache: utils.NewMeteredCache(32*1024, "", 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,7 +955,7 @@ func NewTestTree(diskdb ethdb.KeyValueStore, blockHash, root common.Hash) *Tree
|
||||
diskdb: diskdb,
|
||||
root: root,
|
||||
blockHash: blockHash,
|
||||
cache: utils.NewMeteredCache(128*256, "", "", 0),
|
||||
cache: utils.NewMeteredCache(128*256, "", 0),
|
||||
created: time.Now(),
|
||||
}
|
||||
return &Tree{
|
||||
|
||||
@@ -51,11 +51,10 @@ func randomHash() common.Hash {
|
||||
|
||||
// randomAccount generates a random account and returns it RLP encoded.
|
||||
func randomAccount() []byte {
|
||||
root := randomHash()
|
||||
a := Account{
|
||||
a := &types.StateAccount{
|
||||
Balance: big.NewInt(rand.Int63()),
|
||||
Nonce: rand.Uint64(),
|
||||
Root: root[:],
|
||||
Root: randomHash(),
|
||||
CodeHash: types.EmptyCodeHash[:],
|
||||
}
|
||||
data, _ := rlp.EncodeToBytes(a)
|
||||
@@ -696,7 +695,7 @@ func TestReadStateDuringFlattening(t *testing.T) {
|
||||
snap := snaps.Snapshot(diffRootC)
|
||||
|
||||
// Register the testing hook to access the state after flattening
|
||||
var result = make(chan *Account)
|
||||
var result = make(chan *types.SlimAccount)
|
||||
snaps.onFlatten = func() {
|
||||
// Spin up a thread to read the account from the pre-created
|
||||
// snapshot handler. It's expected to be blocked.
|
||||
|
||||
+120
-92
@@ -31,7 +31,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
@@ -68,33 +67,38 @@ func (s Storage) Copy() Storage {
|
||||
// stateObject represents an Ethereum account which is being modified.
|
||||
//
|
||||
// The usage pattern is as follows:
|
||||
// First you need to obtain a state object.
|
||||
// Account values can be accessed and modified through the object.
|
||||
// Finally, call commitTrie to write the modified storage trie into a database.
|
||||
// - First you need to obtain a state object.
|
||||
// - Account values as well as storages can be accessed and modified through the object.
|
||||
// - Finally, call commit to return the changes of storage trie and update account data.
|
||||
type stateObject struct {
|
||||
address common.Address
|
||||
addrHash common.Hash // hash of ethereum address of the account
|
||||
// dataLock protects the [data] field to prevent a race condition
|
||||
// in the transaction pool tests. TODO remove after re-implementing
|
||||
// tx pool to be synchronous.
|
||||
dataLock sync.RWMutex
|
||||
data types.StateAccount
|
||||
db *StateDB
|
||||
address common.Address // address of ethereum account
|
||||
addrHash common.Hash // hash of ethereum address of the account
|
||||
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent
|
||||
data types.StateAccount // Account data with all mutations applied in the scope of block
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
code Code // contract bytecode, which gets set when code is loaded
|
||||
|
||||
originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
|
||||
originStorage Storage // Storage cache of original entries to dedup rewrites
|
||||
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
|
||||
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution
|
||||
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction
|
||||
|
||||
// Cache flags.
|
||||
// When an object is marked suicided it will be deleted from the trie
|
||||
// during the "update" phase of the state transition.
|
||||
dirtyCode bool // true if the code was updated
|
||||
suicided bool
|
||||
deleted bool
|
||||
|
||||
// Flag whether the account was marked as self-destructed. The self-destructed account
|
||||
// is still accessible in the scope of same transaction.
|
||||
selfDestructed bool
|
||||
|
||||
// Flag whether the account was marked as deleted. A self-destructed account
|
||||
// or an account that is considered as empty will be marked as deleted at
|
||||
// the end of transaction and no longer accessible anymore.
|
||||
deleted bool
|
||||
|
||||
// Flag whether the object was created in the current transaction
|
||||
created bool
|
||||
}
|
||||
|
||||
// empty returns whether the account is considered empty.
|
||||
@@ -103,21 +107,17 @@ func (s *stateObject) empty() bool {
|
||||
}
|
||||
|
||||
// newObject creates a state object.
|
||||
func newObject(db *StateDB, address common.Address, data types.StateAccount) *stateObject {
|
||||
if data.Balance == nil {
|
||||
data.Balance = new(big.Int)
|
||||
}
|
||||
if data.CodeHash == nil {
|
||||
data.CodeHash = types.EmptyCodeHash.Bytes()
|
||||
}
|
||||
if data.Root == (common.Hash{}) {
|
||||
data.Root = types.EmptyRootHash
|
||||
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
|
||||
origin := acct
|
||||
if acct == nil {
|
||||
acct = types.NewEmptyStateAccount()
|
||||
}
|
||||
return &stateObject{
|
||||
db: db,
|
||||
address: address,
|
||||
addrHash: crypto.Keccak256Hash(address[:]),
|
||||
data: data,
|
||||
origin: origin,
|
||||
data: *acct,
|
||||
originStorage: make(Storage),
|
||||
pendingStorage: make(Storage),
|
||||
dirtyStorage: make(Storage),
|
||||
@@ -129,8 +129,8 @@ func (s *stateObject) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, &s.data)
|
||||
}
|
||||
|
||||
func (s *stateObject) markSuicided() {
|
||||
s.suicided = true
|
||||
func (s *stateObject) markSelfdestructed() {
|
||||
s.selfDestructed = true
|
||||
}
|
||||
|
||||
func (s *stateObject) touch() {
|
||||
@@ -147,17 +147,15 @@ func (s *stateObject) touch() {
|
||||
// getTrie returns the associated storage trie. The trie will be opened
|
||||
// if it's not loaded previously. An error will be returned if trie can't
|
||||
// be loaded.
|
||||
func (s *stateObject) getTrie(db Database) (Trie, error) {
|
||||
func (s *stateObject) getTrie() (Trie, error) {
|
||||
if s.trie == nil {
|
||||
// Try fetching from prefetcher first
|
||||
// We don't prefetch empty tries
|
||||
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil {
|
||||
// When the miner is creating the pending state, there is no
|
||||
// prefetcher
|
||||
// When the miner is creating the pending state, there is no prefetcher
|
||||
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
|
||||
}
|
||||
if s.trie == nil {
|
||||
tr, err := db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root)
|
||||
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -168,18 +166,18 @@ func (s *stateObject) getTrie(db Database) (Trie, error) {
|
||||
}
|
||||
|
||||
// GetState retrieves a value from the account storage trie.
|
||||
func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
|
||||
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
||||
// If we have a dirty value for this state entry, return it
|
||||
value, dirty := s.dirtyStorage[key]
|
||||
if dirty {
|
||||
return value
|
||||
}
|
||||
// Otherwise return the entry's original value
|
||||
return s.GetCommittedState(db, key)
|
||||
return s.GetCommittedState(key)
|
||||
}
|
||||
|
||||
// GetCommittedState retrieves a value from the committed account storage trie.
|
||||
func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
|
||||
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
||||
// If we have a pending write or clean cached, return that
|
||||
if value, pending := s.pendingStorage[key]; pending {
|
||||
return value
|
||||
@@ -198,8 +196,9 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
|
||||
}
|
||||
// If no live objects are available, attempt to use snapshots
|
||||
var (
|
||||
enc []byte
|
||||
err error
|
||||
enc []byte
|
||||
err error
|
||||
value common.Hash
|
||||
)
|
||||
if s.db.snap != nil {
|
||||
start := time.Now()
|
||||
@@ -207,16 +206,23 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
|
||||
if metrics.EnabledExpensive {
|
||||
s.db.SnapshotStorageReads += time.Since(start)
|
||||
}
|
||||
if len(enc) > 0 {
|
||||
_, content, _, err := rlp.Split(enc)
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
}
|
||||
value.SetBytes(content)
|
||||
}
|
||||
}
|
||||
// If the snapshot is unavailable or reading from it fails, load from the database.
|
||||
if s.db.snap == nil || err != nil {
|
||||
start := time.Now()
|
||||
tr, err := s.getTrie(db)
|
||||
tr, err := s.getTrie()
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
return common.Hash{}
|
||||
}
|
||||
enc, err = tr.GetStorage(s.address, key.Bytes())
|
||||
val, err := tr.GetStorage(s.address, key.Bytes())
|
||||
if metrics.EnabledExpensive {
|
||||
s.db.StorageReads += time.Since(start)
|
||||
}
|
||||
@@ -224,23 +230,16 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
|
||||
s.db.setError(err)
|
||||
return common.Hash{}
|
||||
}
|
||||
}
|
||||
var value common.Hash
|
||||
if len(enc) > 0 {
|
||||
_, content, _, err := rlp.Split(enc)
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
}
|
||||
value.SetBytes(content)
|
||||
value.SetBytes(val)
|
||||
}
|
||||
s.originStorage[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// SetState updates a value in account storage.
|
||||
func (s *stateObject) SetState(db Database, key, value common.Hash) {
|
||||
func (s *stateObject) SetState(key, value common.Hash) {
|
||||
// If the new value is the same as old, don't set
|
||||
prev := s.GetState(db, key)
|
||||
prev := s.GetState(key)
|
||||
if prev == value {
|
||||
return
|
||||
}
|
||||
@@ -278,7 +277,7 @@ func (s *stateObject) finalise(prefetch bool) {
|
||||
// updateTrie writes cached storage modifications into the object's storage trie.
|
||||
// It will return nil if the trie has not been loaded and no changes have been
|
||||
// made. An error will be returned if the trie can't be loaded/updated correctly.
|
||||
func (s *stateObject) updateTrie(db Database) (Trie, error) {
|
||||
func (s *stateObject) updateTrie() (Trie, error) {
|
||||
// Make sure all dirty slots are finalized into the pending storage area
|
||||
s.finalise(false) // Don't prefetch anymore, pull directly if need be
|
||||
if len(s.pendingStorage) == 0 {
|
||||
@@ -291,9 +290,10 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) {
|
||||
// The snapshot storage map for the object
|
||||
var (
|
||||
storage map[common.Hash][]byte
|
||||
origin map[common.Hash][]byte
|
||||
hasher = s.db.hasher
|
||||
)
|
||||
tr, err := s.getTrie(db)
|
||||
tr, err := s.getTrie()
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
return nil, err
|
||||
@@ -305,9 +305,11 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) {
|
||||
if value == s.originStorage[key] {
|
||||
continue
|
||||
}
|
||||
prev := s.originStorage[key]
|
||||
s.originStorage[key] = value
|
||||
|
||||
var v []byte
|
||||
// rlp-encoded value to be used by the snapshot
|
||||
var snapshotVal []byte
|
||||
if (value == common.Hash{}) {
|
||||
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
|
||||
s.db.setError(err)
|
||||
@@ -315,25 +317,43 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) {
|
||||
}
|
||||
s.db.StorageDeleted += 1
|
||||
} else {
|
||||
trimmedVal := common.TrimLeftZeroes(value[:])
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
|
||||
if err := tr.UpdateStorage(s.address, key[:], v); err != nil {
|
||||
snapshotVal, _ = rlp.EncodeToBytes(trimmedVal)
|
||||
if err := tr.UpdateStorage(s.address, key[:], trimmedVal); err != nil {
|
||||
s.db.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
s.db.StorageUpdated += 1
|
||||
}
|
||||
// If state snapshotting is active, cache the data til commit
|
||||
if s.db.snap != nil {
|
||||
if storage == nil {
|
||||
// Retrieve the old storage map, if available, create a new one otherwise
|
||||
if storage = s.db.snapStorage[s.addrHash]; storage == nil {
|
||||
storage = make(map[common.Hash][]byte)
|
||||
s.db.snapStorage[s.addrHash] = storage
|
||||
}
|
||||
// Cache the mutated storage slots until commit
|
||||
if storage == nil {
|
||||
if storage = s.db.storages[s.addrHash]; storage == nil {
|
||||
storage = make(map[common.Hash][]byte)
|
||||
s.db.storages[s.addrHash] = storage
|
||||
}
|
||||
storage[crypto.HashData(hasher, key[:])] = v // v will be nil if it's deleted
|
||||
}
|
||||
khash := crypto.HashData(hasher, key[:])
|
||||
storage[khash] = snapshotVal // snapshotVal will be nil if it's deleted
|
||||
|
||||
// Cache the original value of mutated storage slots
|
||||
if origin == nil {
|
||||
if origin = s.db.storagesOrigin[s.address]; origin == nil {
|
||||
origin = make(map[common.Hash][]byte)
|
||||
s.db.storagesOrigin[s.address] = origin
|
||||
}
|
||||
}
|
||||
// Track the original value of slot only if it's mutated first time
|
||||
if _, ok := origin[khash]; !ok {
|
||||
if prev == (common.Hash{}) {
|
||||
origin[khash] = nil // nil if it was not present previously
|
||||
} else {
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
b, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(prev[:]))
|
||||
origin[khash] = b
|
||||
}
|
||||
}
|
||||
// Cache the items for preloading
|
||||
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
|
||||
}
|
||||
if s.db.prefetcher != nil {
|
||||
@@ -347,8 +367,8 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) {
|
||||
|
||||
// UpdateRoot sets the trie root to the current root hash of. An error
|
||||
// will be returned if trie root hash is not computed correctly.
|
||||
func (s *stateObject) updateRoot(db Database) {
|
||||
tr, err := s.updateTrie(db)
|
||||
func (s *stateObject) updateRoot() {
|
||||
tr, err := s.updateTrie()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -363,23 +383,29 @@ func (s *stateObject) updateRoot(db Database) {
|
||||
s.data.Root = tr.Hash()
|
||||
}
|
||||
|
||||
// commitTrie submits the storage changes into the storage trie and re-computes
|
||||
// the root. Besides, all trie changes will be collected in a nodeset and returned.
|
||||
func (s *stateObject) commitTrie(db Database) (*trienode.NodeSet, error) {
|
||||
tr, err := s.updateTrie(db)
|
||||
// commit returns the changes made in storage trie and updates the account data.
|
||||
func (s *stateObject) commit() (*trienode.NodeSet, error) {
|
||||
tr, err := s.updateTrie()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If nothing changed, don't bother with committing anything
|
||||
if tr == nil {
|
||||
s.origin = s.data.Copy()
|
||||
return nil, nil
|
||||
}
|
||||
// Track the amount of time wasted on committing the storage trie
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
|
||||
}
|
||||
root, nodes := tr.Commit(false)
|
||||
root, nodes, err := tr.Commit(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.data.Root = root
|
||||
|
||||
// Update original account data after commit
|
||||
s.origin = s.data.Copy()
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
@@ -419,18 +445,24 @@ func (s *stateObject) setBalance(amount *big.Int) {
|
||||
}
|
||||
|
||||
func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||
stateObject := newObject(db, s.address, s.data)
|
||||
if s.trie != nil {
|
||||
stateObject.trie = db.db.CopyTrie(s.trie)
|
||||
obj := &stateObject{
|
||||
db: db,
|
||||
address: s.address,
|
||||
addrHash: s.addrHash,
|
||||
origin: s.origin,
|
||||
data: s.data,
|
||||
}
|
||||
stateObject.code = s.code
|
||||
stateObject.dirtyStorage = s.dirtyStorage.Copy()
|
||||
stateObject.originStorage = s.originStorage.Copy()
|
||||
stateObject.pendingStorage = s.pendingStorage.Copy()
|
||||
stateObject.suicided = s.suicided
|
||||
stateObject.dirtyCode = s.dirtyCode
|
||||
stateObject.deleted = s.deleted
|
||||
return stateObject
|
||||
if s.trie != nil {
|
||||
obj.trie = db.db.CopyTrie(s.trie)
|
||||
}
|
||||
obj.code = s.code
|
||||
obj.dirtyStorage = s.dirtyStorage.Copy()
|
||||
obj.originStorage = s.originStorage.Copy()
|
||||
obj.pendingStorage = s.pendingStorage.Copy()
|
||||
obj.selfDestructed = s.selfDestructed
|
||||
obj.dirtyCode = s.dirtyCode
|
||||
obj.deleted = s.deleted
|
||||
return obj
|
||||
}
|
||||
|
||||
//
|
||||
@@ -443,14 +475,14 @@ func (s *stateObject) Address() common.Address {
|
||||
}
|
||||
|
||||
// Code returns the contract code associated with this object, if any.
|
||||
func (s *stateObject) Code(db Database) []byte {
|
||||
func (s *stateObject) Code() []byte {
|
||||
if s.code != nil {
|
||||
return s.code
|
||||
}
|
||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||
return nil
|
||||
}
|
||||
code, err := db.ContractCode(s.addrHash, common.BytesToHash(s.CodeHash()))
|
||||
code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if err != nil {
|
||||
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
|
||||
}
|
||||
@@ -461,14 +493,14 @@ func (s *stateObject) Code(db Database) []byte {
|
||||
// CodeSize returns the size of the contract code associated with this object,
|
||||
// or zero if none. This method is an almost mirror of Code, but uses a cache
|
||||
// inside the database to avoid loading codes seen recently.
|
||||
func (s *stateObject) CodeSize(db Database) int {
|
||||
func (s *stateObject) CodeSize() int {
|
||||
if s.code != nil {
|
||||
return len(s.code)
|
||||
}
|
||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||
return 0
|
||||
}
|
||||
size, err := db.ContractCodeSize(s.addrHash, common.BytesToHash(s.CodeHash()))
|
||||
size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if err != nil {
|
||||
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
|
||||
}
|
||||
@@ -476,7 +508,7 @@ func (s *stateObject) CodeSize(db Database) int {
|
||||
}
|
||||
|
||||
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
|
||||
prevcode := s.Code(s.db.db)
|
||||
prevcode := s.Code()
|
||||
s.db.journal.append(codeChange{
|
||||
account: &s.address,
|
||||
prevhash: s.CodeHash(),
|
||||
@@ -500,8 +532,6 @@ func (s *stateObject) SetNonce(nonce uint64) {
|
||||
}
|
||||
|
||||
func (s *stateObject) setNonce(nonce uint64) {
|
||||
s.dataLock.Lock()
|
||||
defer s.dataLock.Unlock()
|
||||
s.data.Nonce = nonce
|
||||
}
|
||||
|
||||
@@ -514,7 +544,5 @@ func (s *stateObject) Balance() *big.Int {
|
||||
}
|
||||
|
||||
func (s *stateObject) Nonce() uint64 {
|
||||
s.dataLock.RLock()
|
||||
defer s.dataLock.RUnlock()
|
||||
return s.data.Nonce
|
||||
}
|
||||
|
||||
@@ -40,21 +40,22 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
type stateTest struct {
|
||||
type stateEnv struct {
|
||||
db ethdb.Database
|
||||
state *StateDB
|
||||
}
|
||||
|
||||
func newStateTest() *stateTest {
|
||||
func newStateEnv() *stateEnv {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
sdb, _ := New(types.EmptyRootHash, NewDatabase(db), nil)
|
||||
return &stateTest{db: db, state: sdb}
|
||||
return &stateEnv{db: db, state: sdb}
|
||||
}
|
||||
|
||||
func TestIterativeDump(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
sdb, _ := New(types.EmptyRootHash, NewDatabaseWithConfig(db, &trie.Config{Preimages: true}), nil)
|
||||
s := &stateTest{db: db, state: sdb}
|
||||
tdb := NewDatabaseWithConfig(db, &trie.Config{Preimages: true})
|
||||
sdb, _ := New(types.EmptyRootHash, tdb, nil)
|
||||
s := &stateEnv{db: db, state: sdb}
|
||||
|
||||
// generate a few entries
|
||||
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
|
||||
@@ -69,7 +70,8 @@ func TestIterativeDump(t *testing.T) {
|
||||
// write some of them to the trie
|
||||
s.state.updateStateObject(obj1)
|
||||
s.state.updateStateObject(obj2)
|
||||
s.state.Commit(false, false)
|
||||
root, _ := s.state.Commit(0, false, false)
|
||||
s.state, _ = New(root, tdb, nil)
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
s.state.IterativeDump(nil, json.NewEncoder(b))
|
||||
|
||||
+365
-133
@@ -42,6 +42,7 @@ import (
|
||||
"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"
|
||||
@@ -67,27 +68,38 @@ func (n *proofList) Delete(key []byte) error {
|
||||
// StateDB structs within the ethereum protocol are used to store anything
|
||||
// within the merkle trie. StateDBs take care of caching and storing
|
||||
// nested states. It's the general query interface to retrieve:
|
||||
//
|
||||
// * Contracts
|
||||
// * Accounts
|
||||
//
|
||||
// Once the state is committed, tries cached in stateDB (including account
|
||||
// trie, storage tries) will no longer be functional. A new state instance
|
||||
// must be created with new root and updated database for accessing post-
|
||||
// commit states.
|
||||
type StateDB struct {
|
||||
db Database
|
||||
prefetcher *triePrefetcher
|
||||
trie Trie
|
||||
hasher crypto.KeccakState
|
||||
snap snapshot.Snapshot // Nil if snapshot is not available
|
||||
|
||||
// originalRoot is the pre-state root, before any changes were made.
|
||||
// It will be updated when the Commit is called.
|
||||
originalRoot common.Hash
|
||||
|
||||
snap snapshot.Snapshot
|
||||
snapAccounts map[common.Hash][]byte
|
||||
snapStorage map[common.Hash]map[common.Hash][]byte
|
||||
// These maps hold the state changes (including the corresponding
|
||||
// original value) that occurred in this **block**.
|
||||
accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding
|
||||
storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format
|
||||
accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
|
||||
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
|
||||
|
||||
// This map holds 'live' objects, which will get modified while processing a state transition.
|
||||
// This map holds 'live' objects, which will get modified while processing
|
||||
// a state transition.
|
||||
stateObjects map[common.Address]*stateObject
|
||||
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
|
||||
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
|
||||
stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block
|
||||
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
|
||||
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
|
||||
stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
@@ -101,11 +113,13 @@ type StateDB struct {
|
||||
// The refund counter, also used by state transitioning.
|
||||
refund uint64
|
||||
|
||||
// The tx context and all occurred logs in the scope of transaction.
|
||||
thash common.Hash
|
||||
txIndex int
|
||||
logs map[common.Hash][]*types.Log
|
||||
logSize uint
|
||||
|
||||
// Preimages occurred seen by VM in the scope of block.
|
||||
preimages map[common.Hash][]byte
|
||||
|
||||
// Per-transaction access list
|
||||
@@ -165,10 +179,14 @@ func NewWithSnapshot(root common.Hash, db Database, snap snapshot.Snapshot) (*St
|
||||
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]struct{}),
|
||||
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
|
||||
logs: make(map[common.Hash][]*types.Log),
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
journal: newJournal(),
|
||||
@@ -182,8 +200,6 @@ func NewWithSnapshot(root common.Hash, db Database, snap snapshot.Snapshot) (*St
|
||||
return nil, fmt.Errorf("cannot create new statedb for root: %s, using snapshot with mismatched root: %s", root, snap.Root().Hex())
|
||||
}
|
||||
sdb.snap = snap
|
||||
sdb.snapAccounts = make(map[common.Hash][]byte)
|
||||
sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
|
||||
}
|
||||
return sdb, nil
|
||||
}
|
||||
@@ -296,7 +312,7 @@ func (s *StateDB) AddRefund(gas uint64) {
|
||||
}
|
||||
|
||||
// SubRefund removes gas from the refund counter.
|
||||
// This method will panic if the refund counter goes below zero
|
||||
// This method will set the refund counter to 0 if the gas is greater than the current refund.
|
||||
func (s *StateDB) SubRefund(gas uint64) {
|
||||
s.journal.append(refundChange{prev: s.refund})
|
||||
if gas > s.refund {
|
||||
@@ -308,7 +324,7 @@ func (s *StateDB) SubRefund(gas uint64) {
|
||||
}
|
||||
|
||||
// Exist reports whether the given account address exists in the state.
|
||||
// Notably this also returns true for suicided accounts.
|
||||
// Notably this also returns true for self-destructed accounts.
|
||||
func (s *StateDB) Exist(addr common.Address) bool {
|
||||
return s.getStateObject(addr) != nil
|
||||
}
|
||||
@@ -346,7 +362,7 @@ func (s *StateDB) TxIndex() int {
|
||||
func (s *StateDB) GetCode(addr common.Address) []byte {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.Code(s.db)
|
||||
return stateObject.Code()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -354,7 +370,7 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
|
||||
func (s *StateDB) GetCodeSize(addr common.Address) int {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.CodeSize(s.db)
|
||||
return stateObject.CodeSize()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -371,7 +387,7 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.GetState(s.db, hash)
|
||||
return stateObject.GetState(hash)
|
||||
}
|
||||
return common.Hash{}
|
||||
}
|
||||
@@ -384,7 +400,7 @@ func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) {
|
||||
// GetProofByHash returns the Merkle proof for a given account.
|
||||
func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) {
|
||||
var proof proofList
|
||||
err := s.trie.Prove(addrHash[:], 0, &proof)
|
||||
err := s.trie.Prove(addrHash[:], &proof)
|
||||
return proof, err
|
||||
}
|
||||
|
||||
@@ -398,7 +414,7 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
|
||||
return nil, errors.New("storage trie for requested address does not exist")
|
||||
}
|
||||
var proof proofList
|
||||
err = trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
|
||||
err = trie.Prove(crypto.Keccak256(key.Bytes()), &proof)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -409,7 +425,7 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
|
||||
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.GetCommittedState(s.db, hash)
|
||||
return stateObject.GetCommittedState(hash)
|
||||
}
|
||||
return common.Hash{}
|
||||
}
|
||||
@@ -428,16 +444,16 @@ func (s *StateDB) StorageTrie(addr common.Address) (Trie, error) {
|
||||
return nil, nil
|
||||
}
|
||||
cpy := stateObject.deepCopy(s)
|
||||
if _, err := cpy.updateTrie(s.db); err != nil {
|
||||
if _, err := cpy.updateTrie(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cpy.getTrie(s.db)
|
||||
return cpy.getTrie()
|
||||
}
|
||||
|
||||
func (s *StateDB) HasSuicided(addr common.Address) bool {
|
||||
func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.suicided
|
||||
return stateObject.selfDestructed
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -486,44 +502,59 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) {
|
||||
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
|
||||
stateObject := s.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.SetState(s.db, key, value)
|
||||
stateObject.SetState(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// SetStorage replaces the entire storage for the specified account with given
|
||||
// storage. This function should only be used for debugging.
|
||||
// storage. This function should only be used for debugging and the mutations
|
||||
// must be discarded afterwards.
|
||||
func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) {
|
||||
// SetStorage needs to wipe existing storage. We achieve this by pretending
|
||||
// that the account self-destructed earlier in this block, by flagging
|
||||
// it in stateObjectsDestruct. The effect of doing so is that storage lookups
|
||||
// will not hit disk, since it is assumed that the disk-data is belonging
|
||||
// to a previous incarnation of the object.
|
||||
s.stateObjectsDestruct[addr] = struct{}{}
|
||||
//
|
||||
// TODO(rjl493456442) this function should only be supported by 'unwritable'
|
||||
// state and all mutations made should all be discarded afterwards.
|
||||
if _, ok := s.stateObjectsDestruct[addr]; !ok {
|
||||
s.stateObjectsDestruct[addr] = nil
|
||||
}
|
||||
stateObject := s.GetOrNewStateObject(addr)
|
||||
for k, v := range storage {
|
||||
stateObject.SetState(s.db, k, v)
|
||||
stateObject.SetState(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Suicide marks the given account as suicided.
|
||||
// SelfDestruct marks the given account as selfdestructed.
|
||||
// This clears the account balance.
|
||||
//
|
||||
// The account's state object is still available until the state is committed,
|
||||
// getStateObject will return a non-nil account after Suicide.
|
||||
func (s *StateDB) Suicide(addr common.Address) bool {
|
||||
// getStateObject will return a non-nil account after SelfDestruct.
|
||||
func (s *StateDB) SelfDestruct(addr common.Address) {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject == nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
s.journal.append(suicideChange{
|
||||
s.journal.append(selfDestructChange{
|
||||
account: &addr,
|
||||
prev: stateObject.suicided,
|
||||
prev: stateObject.selfDestructed,
|
||||
prevbalance: new(big.Int).Set(stateObject.Balance()),
|
||||
})
|
||||
stateObject.markSuicided()
|
||||
stateObject.markSelfdestructed()
|
||||
stateObject.data.Balance = new(big.Int)
|
||||
}
|
||||
|
||||
return true
|
||||
func (s *StateDB) Selfdestruct6780(addr common.Address) {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if stateObject.created {
|
||||
s.SelfDestruct(addr)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTransientState sets transient storage for a given account. It
|
||||
@@ -568,13 +599,24 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
|
||||
if err := s.trie.UpdateAccount(addr, &obj.data); err != nil {
|
||||
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
|
||||
}
|
||||
if obj.dirtyCode {
|
||||
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
}
|
||||
// Cache the data until commit. Note, this update mechanism is not symmetric
|
||||
// to the deletion, because whereas it is enough to track account updates
|
||||
// at commit time, deletions need tracking at transaction boundary level to
|
||||
// ensure we capture state clearing.
|
||||
s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data)
|
||||
|
||||
// If state snapshotting is active, cache the data til commit. Note, this
|
||||
// update mechanism is not symmetric to the deletion, because whereas it is
|
||||
// enough to track account updates at commit time, deletions need tracking
|
||||
// at transaction boundary level to ensure we capture state clearing.
|
||||
if s.snap != nil {
|
||||
s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash)
|
||||
// Track the original value of mutated account, nil means it was not present.
|
||||
// Skip if it has been tracked (because updateStateObject may be called
|
||||
// multiple times in a block).
|
||||
if _, ok := s.accountsOrigin[obj.address]; !ok {
|
||||
if obj.origin == nil {
|
||||
s.accountsOrigin[obj.address] = nil
|
||||
} else {
|
||||
s.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,7 +695,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
}
|
||||
}
|
||||
// Insert into the live set
|
||||
obj := newObject(s, addr, *data)
|
||||
obj := newObject(s, addr, data)
|
||||
s.setStateObject(obj)
|
||||
return obj
|
||||
}
|
||||
@@ -675,20 +717,40 @@ func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
|
||||
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that!
|
||||
|
||||
var prevdestruct bool
|
||||
if prev != nil {
|
||||
_, prevdestruct = s.stateObjectsDestruct[prev.address]
|
||||
if !prevdestruct {
|
||||
s.stateObjectsDestruct[prev.address] = struct{}{}
|
||||
}
|
||||
}
|
||||
newobj = newObject(s, addr, types.StateAccount{})
|
||||
newobj = newObject(s, addr, nil)
|
||||
if prev == nil {
|
||||
s.journal.append(createObjectChange{account: &addr})
|
||||
} else {
|
||||
s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
|
||||
// The original account should be marked as destructed and all cached
|
||||
// account and storage data should be cleared as well. Note, it must
|
||||
// be done here, otherwise the destruction event of "original account"
|
||||
// will be lost.
|
||||
_, prevdestruct := s.stateObjectsDestruct[prev.address]
|
||||
if !prevdestruct {
|
||||
s.stateObjectsDestruct[prev.address] = prev.origin
|
||||
}
|
||||
// There may be some cached account/storage data already since IntermediateRoot
|
||||
// will be called for each transaction before byzantium fork which will always
|
||||
// cache the latest account/storage data.
|
||||
prevAccount, ok := s.accountsOrigin[prev.address]
|
||||
s.journal.append(resetObjectChange{
|
||||
account: &addr,
|
||||
prev: prev,
|
||||
prevdestruct: prevdestruct,
|
||||
prevAccount: s.accounts[prev.addrHash],
|
||||
prevStorage: s.storages[prev.addrHash],
|
||||
prevAccountOriginExist: ok,
|
||||
prevAccountOrigin: prevAccount,
|
||||
prevStorageOrigin: s.storagesOrigin[prev.address],
|
||||
})
|
||||
delete(s.accounts, prev.addrHash)
|
||||
delete(s.storages, prev.addrHash)
|
||||
delete(s.accountsOrigin, prev.address)
|
||||
delete(s.storagesOrigin, prev.address)
|
||||
}
|
||||
|
||||
newobj.created = true
|
||||
|
||||
s.setStateObject(newobj)
|
||||
if prev != nil && !prev.deleted {
|
||||
return newobj, prev
|
||||
@@ -713,19 +775,23 @@ func (s *StateDB) CreateAccount(addr common.Address) {
|
||||
}
|
||||
}
|
||||
|
||||
func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
|
||||
so := db.getStateObject(addr)
|
||||
func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
|
||||
so := s.getStateObject(addr)
|
||||
if so == nil {
|
||||
return nil
|
||||
}
|
||||
tr, err := so.getTrie(db.db)
|
||||
tr, err := so.getTrie()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it := trie.NewIterator(tr.NodeIterator(nil))
|
||||
trieIt, err := tr.NodeIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it := trie.NewIterator(trieIt)
|
||||
|
||||
for it.Next() {
|
||||
key := common.BytesToHash(db.trie.GetKey(it.Key))
|
||||
key := common.BytesToHash(s.trie.GetKey(it.Key))
|
||||
if value, dirty := so.dirtyStorage[key]; dirty {
|
||||
if !cb(key, value) {
|
||||
return nil
|
||||
@@ -767,16 +833,26 @@ func (s *StateDB) Copy() *StateDB {
|
||||
db: s.db,
|
||||
trie: s.db.CopyTrie(s.trie),
|
||||
originalRoot: s.originalRoot,
|
||||
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, len(s.journal.dirties)),
|
||||
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
|
||||
stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)),
|
||||
stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)),
|
||||
refund: s.refund,
|
||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||
logSize: s.logSize,
|
||||
preimages: make(map[common.Hash][]byte, len(s.preimages)),
|
||||
journal: newJournal(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
|
||||
// In order for the block producer to be able to use and make additions
|
||||
// to the snapshot tree, we need to copy that as well. Otherwise, any
|
||||
// block mined by ourselves will cause gaps in the tree, and force the
|
||||
// miner to operate trie-backed only.
|
||||
snap: s.snap,
|
||||
}
|
||||
// Copy the dirty states, logs, and preimages
|
||||
for addr := range s.journal.dirties {
|
||||
@@ -810,10 +886,18 @@ func (s *StateDB) Copy() *StateDB {
|
||||
}
|
||||
state.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
// Deep copy the destruction flag.
|
||||
for addr := range s.stateObjectsDestruct {
|
||||
state.stateObjectsDestruct[addr] = struct{}{}
|
||||
// Deep copy the destruction markers.
|
||||
for addr, value := range s.stateObjectsDestruct {
|
||||
state.stateObjectsDestruct[addr] = value
|
||||
}
|
||||
// Deep copy the state changes made in the scope of block
|
||||
// along with their original values.
|
||||
state.accounts = copySet(s.accounts)
|
||||
state.storages = copy2DSet(s.storages)
|
||||
state.accountsOrigin = copySet(state.accountsOrigin)
|
||||
state.storagesOrigin = copy2DSet(state.storagesOrigin)
|
||||
|
||||
// Deep copy the logs occurred in the scope of block
|
||||
for hash, logs := range s.logs {
|
||||
cpy := make([]*types.Log, len(logs))
|
||||
for i, l := range logs {
|
||||
@@ -822,6 +906,7 @@ func (s *StateDB) Copy() *StateDB {
|
||||
}
|
||||
state.logs[hash] = cpy
|
||||
}
|
||||
// Deep copy the preimages occurred in the scope of block
|
||||
for hash, preimage := range s.preimages {
|
||||
state.preimages[hash] = preimage
|
||||
}
|
||||
@@ -841,27 +926,6 @@ func (s *StateDB) Copy() *StateDB {
|
||||
if s.prefetcher != nil {
|
||||
state.prefetcher = s.prefetcher.copy()
|
||||
}
|
||||
if s.snap != nil {
|
||||
// In order for the miner to be able to use and make additions
|
||||
// to the snapshot tree, we need to copy that as well.
|
||||
// Otherwise, any block mined by ourselves will cause gaps in the tree,
|
||||
// and force the miner to operate trie-backed only
|
||||
state.snap = s.snap
|
||||
|
||||
// deep copy needed
|
||||
state.snapAccounts = make(map[common.Hash][]byte, len(s.snapAccounts))
|
||||
for k, v := range s.snapAccounts {
|
||||
state.snapAccounts[k] = v
|
||||
}
|
||||
state.snapStorage = make(map[common.Hash]map[common.Hash][]byte, len(s.snapStorage))
|
||||
for k, v := range s.snapStorage {
|
||||
temp := make(map[common.Hash][]byte, len(v))
|
||||
for kk, vv := range v {
|
||||
temp[kk] = vv
|
||||
}
|
||||
state.snapStorage[k] = temp
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -910,24 +974,26 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||
// Thus, we can safely ignore it here
|
||||
continue
|
||||
}
|
||||
if obj.suicided || (deleteEmptyObjects && obj.empty()) {
|
||||
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
||||
obj.deleted = true
|
||||
|
||||
// We need to maintain account deletions explicitly (will remain
|
||||
// set indefinitely).
|
||||
s.stateObjectsDestruct[obj.address] = struct{}{}
|
||||
|
||||
// If state snapshotting is active, also mark the destruction there.
|
||||
// set indefinitely). Note only the first occurred self-destruct
|
||||
// event is tracked.
|
||||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||
s.stateObjectsDestruct[obj.address] = obj.origin
|
||||
}
|
||||
// Note, we can't do this only at the end of a block because multiple
|
||||
// transactions within the same block might self destruct and then
|
||||
// resurrect an account; but the snapshotter needs both events.
|
||||
if s.snap != nil {
|
||||
delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
|
||||
delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
||||
}
|
||||
delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
|
||||
delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
||||
delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect)
|
||||
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
||||
} else {
|
||||
obj.finalise(true) // Prefetch slots in the background
|
||||
}
|
||||
obj.created = false
|
||||
s.stateObjectsPending[addr] = struct{}{}
|
||||
s.stateObjectsDirty[addr] = struct{}{}
|
||||
|
||||
@@ -971,7 +1037,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||
// to pull useful data from disk.
|
||||
for addr := range s.stateObjectsPending {
|
||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
||||
obj.updateRoot(s.db)
|
||||
obj.updateRoot()
|
||||
}
|
||||
}
|
||||
// Now we're about to start to write changes to the trie. The trie is so far
|
||||
@@ -1022,19 +1088,156 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||
s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entries
|
||||
}
|
||||
|
||||
// deleteStorage iterates the storage trie belongs to the account and mark all
|
||||
// slots inside as deleted.
|
||||
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (bool, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||
start := time.Now()
|
||||
tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root)
|
||||
if err != nil {
|
||||
return false, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
|
||||
}
|
||||
it, err := tr.NodeIterator(nil)
|
||||
if err != nil {
|
||||
return false, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err)
|
||||
}
|
||||
var (
|
||||
set = trienode.NewNodeSet(addrHash)
|
||||
slots = make(map[common.Hash][]byte)
|
||||
stateSize common.StorageSize
|
||||
nodeSize common.StorageSize
|
||||
)
|
||||
for it.Next(true) {
|
||||
// arbitrary stateSize limit, make it configurable
|
||||
if stateSize+nodeSize > 512*1024*1024 {
|
||||
log.Info("Skip large storage deletion", "address", addr.Hex(), "states", stateSize, "nodes", nodeSize)
|
||||
if metrics.EnabledExpensive {
|
||||
slotDeletionSkip.Inc(1)
|
||||
}
|
||||
return true, nil, nil, nil
|
||||
}
|
||||
if it.Leaf() {
|
||||
slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob())
|
||||
stateSize += common.StorageSize(common.HashLength + len(it.LeafBlob()))
|
||||
continue
|
||||
}
|
||||
if it.Hash() == (common.Hash{}) {
|
||||
continue
|
||||
}
|
||||
nodeSize += common.StorageSize(len(it.Path()))
|
||||
set.AddNode(it.Path(), trienode.NewDeleted())
|
||||
}
|
||||
if err := it.Error(); err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
if metrics.EnabledExpensive {
|
||||
if int64(len(slots)) > slotDeletionMaxCount.Value() {
|
||||
slotDeletionMaxCount.Update(int64(len(slots)))
|
||||
}
|
||||
if int64(stateSize+nodeSize) > slotDeletionMaxSize.Value() {
|
||||
slotDeletionMaxSize.Update(int64(stateSize + nodeSize))
|
||||
}
|
||||
slotDeletionTimer.UpdateSince(start)
|
||||
slotDeletionCount.Mark(int64(len(slots)))
|
||||
slotDeletionSize.Mark(int64(stateSize + nodeSize))
|
||||
}
|
||||
return false, slots, set, nil
|
||||
}
|
||||
|
||||
// handleDestruction processes all destruction markers and deletes the account
|
||||
// and associated storage slots if necessary. There are four possible situations
|
||||
// here:
|
||||
//
|
||||
// - the account was not existent and be marked as destructed
|
||||
//
|
||||
// - the account was not existent and be marked as destructed,
|
||||
// however, it's resurrected later in the same block.
|
||||
//
|
||||
// - the account was existent and be marked as destructed
|
||||
//
|
||||
// - the account was existent and be marked as destructed,
|
||||
// however it's resurrected later in the same block.
|
||||
//
|
||||
// In case (a), nothing needs be deleted, nil to nil transition can be ignored.
|
||||
//
|
||||
// In case (b), nothing needs be deleted, nil is used as the original value for
|
||||
// newly created account and storages
|
||||
//
|
||||
// In case (c), **original** account along with its storages should be deleted,
|
||||
// with their values be tracked as original value.
|
||||
//
|
||||
// In case (d), **original** account along with its storages should be deleted,
|
||||
// with their values be tracked as original value.
|
||||
func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.Address]struct{}, error) {
|
||||
incomplete := make(map[common.Address]struct{})
|
||||
for addr, prev := range s.stateObjectsDestruct {
|
||||
// The original account was non-existing, and it's marked as destructed
|
||||
// in the scope of block. It can be case (a) or (b).
|
||||
// - for (a), skip it without doing anything.
|
||||
// - for (b), track account's original value as nil. It may overwrite
|
||||
// the data cached in s.accountsOrigin set by 'updateStateObject'.
|
||||
addrHash := crypto.Keccak256Hash(addr[:])
|
||||
if prev == nil {
|
||||
if _, ok := s.accounts[addrHash]; ok {
|
||||
s.accountsOrigin[addr] = nil // case (b)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// It can overwrite the data in s.accountsOrigin set by 'updateStateObject'.
|
||||
s.accountsOrigin[addr] = types.SlimAccountRLP(*prev) // case (c) or (d)
|
||||
|
||||
// Short circuit if the storage was empty.
|
||||
if prev.Root == types.EmptyRootHash {
|
||||
continue
|
||||
}
|
||||
// Remove storage slots belong to the account.
|
||||
aborted, slots, set, err := s.deleteStorage(addr, addrHash, prev.Root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||
}
|
||||
// The storage is too huge to handle, skip it but mark as incomplete.
|
||||
// For case (d), the account is resurrected might with a few slots
|
||||
// created. In this case, wipe the entire storage state diff because
|
||||
// of aborted deletion.
|
||||
if aborted {
|
||||
incomplete[addr] = struct{}{}
|
||||
delete(s.storagesOrigin, addr)
|
||||
continue
|
||||
}
|
||||
if s.storagesOrigin[addr] == nil {
|
||||
s.storagesOrigin[addr] = slots
|
||||
} else {
|
||||
// It can overwrite the data in s.storagesOrigin[addrHash] set by
|
||||
// 'object.updateTrie'.
|
||||
for key, val := range slots {
|
||||
s.storagesOrigin[addr][key] = val
|
||||
}
|
||||
}
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return incomplete, nil
|
||||
}
|
||||
|
||||
// Commit writes the state to the underlying in-memory trie database.
|
||||
func (s *StateDB) Commit(deleteEmptyObjects bool, referenceRoot bool) (common.Hash, error) {
|
||||
return s.commit(deleteEmptyObjects, nil, common.Hash{}, common.Hash{}, referenceRoot)
|
||||
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, referenceRoot bool) (common.Hash, error) {
|
||||
return s.commit(block, deleteEmptyObjects, nil, common.Hash{}, common.Hash{}, referenceRoot)
|
||||
}
|
||||
|
||||
// CommitWithSnap writes the state to the underlying in-memory trie database and
|
||||
// generates a snapshot layer for the newly committed state.
|
||||
func (s *StateDB) CommitWithSnap(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) {
|
||||
return s.commit(deleteEmptyObjects, snaps, blockHash, parentHash, referenceRoot)
|
||||
func (s *StateDB) CommitWithSnap(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) {
|
||||
return s.commit(block, deleteEmptyObjects, snaps, blockHash, parentHash, referenceRoot)
|
||||
}
|
||||
|
||||
// Commit writes the state to the underlying in-memory trie database.
|
||||
func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) {
|
||||
// Once the state is committed, tries cached in stateDB (including account
|
||||
// trie, storage tries) will no longer be functional. A new state instance
|
||||
// must be created with new root and updated database for accessing post-
|
||||
// commit states.
|
||||
//
|
||||
// The associated block number of the state transition is also provided
|
||||
// for more chain context.
|
||||
func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) {
|
||||
// Short circuit in case any database failure occurred earlier.
|
||||
if s.dbErr != nil {
|
||||
return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
||||
@@ -1051,37 +1254,38 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
|
||||
nodes = trienode.NewMergedNodeSet()
|
||||
codeWriter = s.db.DiskDB().NewBatch()
|
||||
)
|
||||
// Handle all state deletions first
|
||||
incomplete, err := s.handleDestruction(nodes)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// Handle all state updates afterwards
|
||||
for addr := range s.stateObjectsDirty {
|
||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
||||
// Write any contract code associated with the state object
|
||||
if obj.code != nil && obj.dirtyCode {
|
||||
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
obj.dirtyCode = false
|
||||
}
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
set, err := obj.commitTrie(s.db)
|
||||
if err != nil {
|
||||
obj := s.stateObjects[addr]
|
||||
if obj.deleted {
|
||||
continue
|
||||
}
|
||||
// Write any contract code associated with the state object
|
||||
if obj.code != nil && obj.dirtyCode {
|
||||
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
obj.dirtyCode = false
|
||||
}
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
set, err := obj.commit()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// Merge the dirty nodes of storage trie into global set. It is possible
|
||||
// that the account was destructed and then resurrected in the same block.
|
||||
// In this case, the node set is shared by both accounts.
|
||||
if set != nil {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// Merge the dirty nodes of storage trie into global set.
|
||||
if set != nil {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
updates, deleted := set.Size()
|
||||
storageTrieNodesUpdated += updates
|
||||
storageTrieNodesDeleted += deleted
|
||||
}
|
||||
updates, deleted := set.Size()
|
||||
storageTrieNodesUpdated += updates
|
||||
storageTrieNodesDeleted += deleted
|
||||
}
|
||||
// If the contract is destructed, the storage is still left in the
|
||||
// database as dangling data. Theoretically it's should be wiped from
|
||||
// database as well, but in hash-based-scheme it's extremely hard to
|
||||
// determine that if the trie nodes are also referenced by other storage,
|
||||
// and in path-based-scheme some technical challenges are still unsolved.
|
||||
// Although it won't affect the correctness but please fix it TODO(rjl493456442).
|
||||
}
|
||||
if len(s.stateObjectsDirty) > 0 {
|
||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
||||
}
|
||||
if codeWriter.ValueSize() > 0 {
|
||||
if err := codeWriter.Write(); err != nil {
|
||||
@@ -1093,7 +1297,10 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
|
||||
if metrics.EnabledExpensive {
|
||||
start = time.Now()
|
||||
}
|
||||
root, set := s.trie.Commit(true)
|
||||
root, set, err := s.trie.Commit(true)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// Merge the dirty nodes of account trie into global set
|
||||
if set != nil {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
@@ -1121,16 +1328,13 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
|
||||
if s.snap == nil {
|
||||
log.Error(fmt.Sprintf("cannot commit with snaps without a pre-existing snap layer, parentHash: %s, blockHash: %s", parentHash, blockHash))
|
||||
}
|
||||
if err := snaps.Update(blockHash, root, parentHash, s.convertAccountSet(s.stateObjectsDestruct), s.snapAccounts, s.snapStorage); err != nil {
|
||||
if err := snaps.Update(blockHash, root, parentHash, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil {
|
||||
log.Warn("Failed to update snapshot tree", "to", root, "err", err)
|
||||
}
|
||||
if metrics.EnabledExpensive {
|
||||
s.SnapshotCommits += time.Since(start)
|
||||
}
|
||||
s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil
|
||||
}
|
||||
if len(s.stateObjectsDestruct) > 0 {
|
||||
s.stateObjectsDestruct = make(map[common.Address]struct{})
|
||||
s.snap = nil
|
||||
}
|
||||
if root == (common.Hash{}) {
|
||||
root = types.EmptyRootHash
|
||||
@@ -1142,11 +1346,11 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
|
||||
if root != origin {
|
||||
start := time.Now()
|
||||
if referenceRoot {
|
||||
if err := s.db.TrieDB().UpdateAndReferenceRoot(root, origin, nodes); err != nil {
|
||||
if err := s.db.TrieDB().UpdateAndReferenceRoot(root, origin, block, nodes, triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
} else {
|
||||
if err := s.db.TrieDB().Update(root, origin, nodes); err != nil {
|
||||
if err := s.db.TrieDB().Update(root, origin, block, nodes, triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
}
|
||||
@@ -1155,6 +1359,13 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
|
||||
s.TrieDBCommits += time.Since(start)
|
||||
}
|
||||
}
|
||||
// Clear all internal flags at the end of commit operation.
|
||||
s.accounts = make(map[common.Hash][]byte)
|
||||
s.storages = make(map[common.Hash]map[common.Hash][]byte)
|
||||
s.accountsOrigin = make(map[common.Address][]byte)
|
||||
s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
||||
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
||||
return root, nil
|
||||
}
|
||||
|
||||
@@ -1263,7 +1474,7 @@ func (s *StateDB) GetPredicateStorageSlots(address common.Address, index int) ([
|
||||
}
|
||||
|
||||
// convertAccountSet converts a provided account set from address keyed to hash keyed.
|
||||
func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.Hash]struct{} {
|
||||
func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount) map[common.Hash]struct{} {
|
||||
ret := make(map[common.Hash]struct{}, len(set))
|
||||
for addr := range set {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
@@ -1280,3 +1491,24 @@ func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.
|
||||
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))
|
||||
for key, val := range set {
|
||||
copied[key] = common.CopyBytes(val)
|
||||
}
|
||||
return copied
|
||||
}
|
||||
|
||||
// copy2DSet returns a two-dimensional deep-copied set.
|
||||
func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.Hash][]byte {
|
||||
copied := make(map[k]map[common.Hash][]byte, len(set))
|
||||
for addr, subset := range set {
|
||||
copied[addr] = make(map[common.Hash][]byte, len(subset))
|
||||
for key, val := range subset {
|
||||
copied[addr][key] = common.CopyBytes(val)
|
||||
}
|
||||
}
|
||||
return copied
|
||||
}
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
// (c) 2024, 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 2023 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 state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/trie"
|
||||
"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/rlp"
|
||||
)
|
||||
|
||||
// A stateTest checks that the state changes are correctly captured. Instances
|
||||
// of this test with pseudorandom content are created by Generate.
|
||||
//
|
||||
// The test works as follows:
|
||||
//
|
||||
// A list of states are created by applying actions. The state changes between
|
||||
// each state instance are tracked and be verified.
|
||||
type stateTest struct {
|
||||
addrs []common.Address // all account addresses
|
||||
actions [][]testAction // modifications to the state, grouped by block
|
||||
chunk int // The number of actions per chunk
|
||||
err error // failure details are reported through this field
|
||||
}
|
||||
|
||||
// newStateTestAction creates a random action that changes state.
|
||||
func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction {
|
||||
actions := []testAction{
|
||||
{
|
||||
name: "SetBalance",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.SetBalance(addr, big.NewInt(a.args[0]))
|
||||
},
|
||||
args: make([]int64, 1),
|
||||
},
|
||||
{
|
||||
name: "SetNonce",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.SetNonce(addr, uint64(a.args[0]))
|
||||
},
|
||||
args: make([]int64, 1),
|
||||
},
|
||||
{
|
||||
name: "SetState",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
var key, val common.Hash
|
||||
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
|
||||
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
|
||||
s.SetState(addr, key, val)
|
||||
},
|
||||
args: make([]int64, 2),
|
||||
},
|
||||
{
|
||||
name: "SetCode",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
code := make([]byte, 16)
|
||||
binary.BigEndian.PutUint64(code, uint64(a.args[0]))
|
||||
binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
|
||||
s.SetCode(addr, code)
|
||||
},
|
||||
args: make([]int64, 2),
|
||||
},
|
||||
{
|
||||
name: "CreateAccount",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.CreateAccount(addr)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Selfdestruct",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.SelfDestruct(addr)
|
||||
},
|
||||
},
|
||||
}
|
||||
var nonRandom = index != -1
|
||||
if index == -1 {
|
||||
index = r.Intn(len(actions))
|
||||
}
|
||||
action := actions[index]
|
||||
var names []string
|
||||
if !action.noAddr {
|
||||
names = append(names, addr.Hex())
|
||||
}
|
||||
for i := range action.args {
|
||||
if nonRandom {
|
||||
action.args[i] = rand.Int63n(10000) + 1 // set balance to non-zero
|
||||
} else {
|
||||
action.args[i] = rand.Int63n(10000)
|
||||
}
|
||||
names = append(names, fmt.Sprint(action.args[i]))
|
||||
}
|
||||
action.name += " " + strings.Join(names, ", ")
|
||||
return action
|
||||
}
|
||||
|
||||
// Generate returns a new snapshot test of the given size. All randomness is
|
||||
// derived from r.
|
||||
func (*stateTest) Generate(r *rand.Rand, size int) reflect.Value {
|
||||
addrs := make([]common.Address, 5)
|
||||
for i := range addrs {
|
||||
addrs[i][0] = byte(i)
|
||||
}
|
||||
actions := make([][]testAction, rand.Intn(5)+1)
|
||||
|
||||
for i := 0; i < len(actions); i++ {
|
||||
actions[i] = make([]testAction, size)
|
||||
for j := range actions[i] {
|
||||
if j == 0 {
|
||||
// Always include a set balance action to make sure
|
||||
// the state changes are not empty.
|
||||
actions[i][j] = newStateTestAction(common.HexToAddress("0xdeadbeef"), r, 0)
|
||||
continue
|
||||
}
|
||||
actions[i][j] = newStateTestAction(addrs[r.Intn(len(addrs))], r, -1)
|
||||
}
|
||||
}
|
||||
chunk := int(math.Sqrt(float64(size)))
|
||||
if size > 0 && chunk == 0 {
|
||||
chunk = 1
|
||||
}
|
||||
return reflect.ValueOf(&stateTest{
|
||||
addrs: addrs,
|
||||
actions: actions,
|
||||
chunk: chunk,
|
||||
})
|
||||
}
|
||||
|
||||
func (test *stateTest) String() string {
|
||||
out := new(bytes.Buffer)
|
||||
for i, actions := range test.actions {
|
||||
fmt.Fprintf(out, "---- block %d ----\n", i)
|
||||
for j, action := range actions {
|
||||
if j%test.chunk == 0 {
|
||||
fmt.Fprintf(out, "---- transaction %d ----\n", j/test.chunk)
|
||||
}
|
||||
fmt.Fprintf(out, "%4d: %s\n", j%test.chunk, action.name)
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (test *stateTest) run() bool {
|
||||
var (
|
||||
roots []common.Hash
|
||||
accountList []map[common.Address][]byte
|
||||
storageList []map[common.Address]map[common.Hash][]byte
|
||||
onCommit = func(states *triestate.Set) {
|
||||
accountList = append(accountList, copySet(states.Accounts))
|
||||
storageList = append(storageList, copy2DSet(states.Storages))
|
||||
}
|
||||
disk = rawdb.NewMemoryDatabase()
|
||||
tdb = trie.NewDatabaseWithConfig(disk, &trie.Config{OnCommit: onCommit})
|
||||
sdb = NewDatabaseWithNodeDB(disk, tdb)
|
||||
byzantium = rand.Intn(2) == 0
|
||||
)
|
||||
for i, actions := range test.actions {
|
||||
root := types.EmptyRootHash
|
||||
if i != 0 {
|
||||
root = roots[len(roots)-1]
|
||||
}
|
||||
state, err := New(root, sdb, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i, action := range actions {
|
||||
if i%test.chunk == 0 && i != 0 {
|
||||
if byzantium {
|
||||
state.Finalise(true) // call finalise at the transaction boundary
|
||||
} else {
|
||||
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
|
||||
}
|
||||
}
|
||||
action.fn(action, state)
|
||||
}
|
||||
if byzantium {
|
||||
state.Finalise(true) // call finalise at the transaction boundary
|
||||
} else {
|
||||
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
|
||||
}
|
||||
nroot, err := state.Commit(0, true, false) // call commit at the block boundary
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if nroot == root {
|
||||
return true // filter out non-change state transition
|
||||
}
|
||||
roots = append(roots, nroot)
|
||||
}
|
||||
for i := 0; i < len(test.actions); i++ {
|
||||
root := types.EmptyRootHash
|
||||
if i != 0 {
|
||||
root = roots[i-1]
|
||||
}
|
||||
test.err = test.verify(root, roots[i], tdb, accountList[i], storageList[i])
|
||||
if test.err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// verifyAccountCreation this function is called once the state diff says that
|
||||
// specific account was not present. A serial of checks will be performed to
|
||||
// ensure the state diff is correct, includes:
|
||||
//
|
||||
// - the account was indeed not present in trie
|
||||
// - the account is present in new trie, nil->nil is regarded as invalid
|
||||
// - the slots transition is correct
|
||||
func (test *stateTest) verifyAccountCreation(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, slots map[common.Hash][]byte) error {
|
||||
// Verify account change
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
oBlob, err := otr.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nBlob, err := ntr.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(oBlob) != 0 {
|
||||
return fmt.Errorf("unexpected account in old trie, %x", addrHash)
|
||||
}
|
||||
if len(nBlob) == 0 {
|
||||
return fmt.Errorf("missing account in new trie, %x", addrHash)
|
||||
}
|
||||
|
||||
// Verify storage changes
|
||||
var nAcct types.StateAccount
|
||||
if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil {
|
||||
return err
|
||||
}
|
||||
// Account has no slot, empty slot set is expected
|
||||
if nAcct.Root == types.EmptyRootHash {
|
||||
if len(slots) != 0 {
|
||||
return fmt.Errorf("unexpected slot changes %x", addrHash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Account has slots, ensure all new slots are contained
|
||||
st, err := trie.New(trie.StorageTrieID(next, addrHash, nAcct.Root), db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range slots {
|
||||
st.Update(key.Bytes(), val)
|
||||
}
|
||||
if st.Hash() != types.EmptyRootHash {
|
||||
return errors.New("invalid slot changes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyAccountUpdate this function is called once the state diff says that
|
||||
// specific account was present. A serial of checks will be performed to
|
||||
// ensure the state diff is correct, includes:
|
||||
//
|
||||
// - the account was indeed present in trie
|
||||
// - the account in old trie matches the provided value
|
||||
// - the slots transition is correct
|
||||
func (test *stateTest) verifyAccountUpdate(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, origin []byte, slots map[common.Hash][]byte) error {
|
||||
// Verify account change
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
oBlob, err := otr.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nBlob, err := ntr.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(oBlob) == 0 {
|
||||
return fmt.Errorf("missing account in old trie, %x", addrHash)
|
||||
}
|
||||
full, err := types.FullAccountRLP(origin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(full, oBlob) {
|
||||
return fmt.Errorf("account value is not matched, %x", addrHash)
|
||||
}
|
||||
|
||||
// Decode accounts
|
||||
var (
|
||||
oAcct types.StateAccount
|
||||
nAcct types.StateAccount
|
||||
nRoot common.Hash
|
||||
)
|
||||
if err := rlp.DecodeBytes(oBlob, &oAcct); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(nBlob) == 0 {
|
||||
nRoot = types.EmptyRootHash
|
||||
} else {
|
||||
if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil {
|
||||
return err
|
||||
}
|
||||
nRoot = nAcct.Root
|
||||
}
|
||||
|
||||
// Verify storage
|
||||
st, err := trie.New(trie.StorageTrieID(next, addrHash, nRoot), db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range slots {
|
||||
st.Update(key.Bytes(), val)
|
||||
}
|
||||
if st.Hash() != oAcct.Root {
|
||||
return errors.New("invalid slot changes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (test *stateTest) verify(root common.Hash, next common.Hash, db *trie.Database, accountsOrigin map[common.Address][]byte, storagesOrigin map[common.Address]map[common.Hash][]byte) error {
|
||||
otr, err := trie.New(trie.StateTrieID(root), db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ntr, err := trie.New(trie.StateTrieID(next), db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for addr, account := range accountsOrigin {
|
||||
var err error
|
||||
if len(account) == 0 {
|
||||
err = test.verifyAccountCreation(next, db, otr, ntr, addr, storagesOrigin[addr])
|
||||
} else {
|
||||
err = test.verifyAccountUpdate(next, db, otr, ntr, addr, accountsOrigin[addr], storagesOrigin[addr])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestStateChanges(t *testing.T) {
|
||||
config := &quick.Config{MaxCount: 1000}
|
||||
err := quick.Check((*stateTest).run, config)
|
||||
if cerr, ok := err.(*quick.CheckError); ok {
|
||||
test := cerr.In[0].(*stateTest)
|
||||
t.Errorf("%v:\n%s", test.err, test)
|
||||
} else if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
+116
-44
@@ -29,6 +29,7 @@ package state
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
@@ -40,8 +41,11 @@ import (
|
||||
"testing/quick"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/state/snapshot"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/trie"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// Tests that updating a state trie does not leak any database writes prior to
|
||||
@@ -112,7 +116,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
}
|
||||
|
||||
// Commit and cross check the databases.
|
||||
transRoot, err := transState.Commit(false, false)
|
||||
transRoot, err := transState.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit transition state: %v", err)
|
||||
}
|
||||
@@ -120,7 +124,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
|
||||
}
|
||||
|
||||
finalRoot, err := finalState.Commit(false, false)
|
||||
finalRoot, err := finalState.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit final state: %v", err)
|
||||
}
|
||||
@@ -307,9 +311,9 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Suicide",
|
||||
name: "SelfDestruct",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.Suicide(addr)
|
||||
s.SelfDestruct(addr)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -459,7 +463,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||
}
|
||||
// Check basic accessor methods.
|
||||
checkeq("Exist", state.Exist(addr), checkstate.Exist(addr))
|
||||
checkeq("HasSuicided", state.HasSuicided(addr), checkstate.HasSuicided(addr))
|
||||
checkeq("HasSelfdestructed", state.HasSelfDestructed(addr), checkstate.HasSelfDestructed(addr))
|
||||
checkeq("GetBalance", state.GetBalance(addr), checkstate.GetBalance(addr))
|
||||
checkeq("GetNonce", state.GetNonce(addr), checkstate.GetNonce(addr))
|
||||
checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr))
|
||||
@@ -491,9 +495,9 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||
}
|
||||
|
||||
func TestTouchDelete(t *testing.T) {
|
||||
s := newStateTest()
|
||||
s := newStateEnv()
|
||||
s.state.GetOrNewStateObject(common.Address{})
|
||||
root, _ := s.state.Commit(false, false)
|
||||
root, _ := s.state.Commit(0, false, false)
|
||||
s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap)
|
||||
|
||||
snapshot := s.state.Snapshot()
|
||||
@@ -528,7 +532,8 @@ func TestCopyOfCopy(t *testing.T) {
|
||||
//
|
||||
// See https://github.com/ethereum/go-ethereum/issues/20106.
|
||||
func TestCopyCommitCopy(t *testing.T) {
|
||||
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
tdb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
state, _ := New(types.EmptyRootHash, tdb, nil)
|
||||
|
||||
// Create an account and check if the retrieved balance is correct
|
||||
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
|
||||
@@ -565,20 +570,6 @@ func TestCopyCommitCopy(t *testing.T) {
|
||||
if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
|
||||
copyOne.Commit(false, false)
|
||||
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("first copy post-commit balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
||||
t.Fatalf("first copy post-commit code mismatch: have %x, want %x", code, []byte("hello"))
|
||||
}
|
||||
if val := copyOne.GetState(addr, skey); val != sval {
|
||||
t.Fatalf("first copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := copyOne.GetCommittedState(addr, skey); val != sval {
|
||||
t.Fatalf("first copy post-commit committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
// Copy the copy and check the balance once more
|
||||
copyTwo := copyOne.Copy()
|
||||
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
@@ -590,8 +581,23 @@ func TestCopyCommitCopy(t *testing.T) {
|
||||
if val := copyTwo.GetState(addr, skey); val != sval {
|
||||
t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := copyTwo.GetCommittedState(addr, skey); val != sval {
|
||||
t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
// Commit state, ensure states can be loaded from disk
|
||||
root, _ := state.Commit(0, false, false)
|
||||
state, _ = New(root, tdb, nil)
|
||||
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
||||
t.Fatalf("state post-commit code mismatch: have %x, want %x", code, []byte("hello"))
|
||||
}
|
||||
if val := state.GetState(addr, skey); val != sval {
|
||||
t.Fatalf("state post-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := state.GetCommittedState(addr, skey); val != sval {
|
||||
t.Fatalf("state post-commit committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,19 +657,6 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
||||
if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
copyTwo.Commit(false, false)
|
||||
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("second copy post-commit balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
||||
t.Fatalf("second copy post-commit code mismatch: have %x, want %x", code, []byte("hello"))
|
||||
}
|
||||
if val := copyTwo.GetState(addr, skey); val != sval {
|
||||
t.Fatalf("second copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := copyTwo.GetCommittedState(addr, skey); val != sval {
|
||||
t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
// Copy the copy-copy and check the balance once more
|
||||
copyThree := copyTwo.Copy()
|
||||
if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
@@ -675,11 +668,56 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
||||
if val := copyThree.GetState(addr, skey); val != sval {
|
||||
t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := copyThree.GetCommittedState(addr, skey); val != sval {
|
||||
if val := copyThree.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitCopy tests the copy from a committed state is not functional.
|
||||
func TestCommitCopy(t *testing.T) {
|
||||
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
|
||||
// Create an account and check if the retrieved balance is correct
|
||||
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
|
||||
skey := common.HexToHash("aaa")
|
||||
sval := common.HexToHash("bbb")
|
||||
|
||||
state.SetBalance(addr, big.NewInt(42)) // Change the account trie
|
||||
state.SetCode(addr, []byte("hello")) // Change an external metadata
|
||||
state.SetState(addr, skey, sval) // Change the storage trie
|
||||
|
||||
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
||||
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
|
||||
}
|
||||
if val := state.GetState(addr, skey); val != sval {
|
||||
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the committed state database, the copied one is not functional.
|
||||
state.Commit(0, true, false)
|
||||
copied := state.Copy()
|
||||
if balance := copied.GetBalance(addr); balance.Cmp(big.NewInt(0)) != 0 {
|
||||
t.Fatalf("unexpected balance: have %v", balance)
|
||||
}
|
||||
if code := copied.GetCode(addr); code != nil {
|
||||
t.Fatalf("unexpected code: have %x", code)
|
||||
}
|
||||
if val := copied.GetState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("unexpected storage slot: have %x", val)
|
||||
}
|
||||
if val := copied.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("unexpected storage slot: have %x", val)
|
||||
}
|
||||
if !errors.Is(copied.Error(), trie.ErrCommitted) {
|
||||
t.Fatalf("unexpected state error, %v", copied.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteCreateRevert tests a weird state transition corner case that we hit
|
||||
// while changing the internals of StateDB. The workflow is that a contract is
|
||||
// self-destructed, then in a follow-up transaction (but same block) it's created
|
||||
@@ -695,11 +733,11 @@ func TestDeleteCreateRevert(t *testing.T) {
|
||||
addr := common.BytesToAddress([]byte("so"))
|
||||
state.SetBalance(addr, big.NewInt(1))
|
||||
|
||||
root, _ := state.Commit(false, false)
|
||||
root, _ := state.Commit(0, false, false)
|
||||
state, _ = NewWithSnapshot(root, state.db, state.snap)
|
||||
|
||||
// Simulate self-destructing in one transaction, then create-reverting in another
|
||||
state.Suicide(addr)
|
||||
state.SelfDestruct(addr)
|
||||
state.Finalise(true)
|
||||
|
||||
id := state.Snapshot()
|
||||
@@ -707,7 +745,7 @@ func TestDeleteCreateRevert(t *testing.T) {
|
||||
state.RevertToSnapshot(id)
|
||||
|
||||
// Commit the entire state and make sure we don't crash and have the correct state
|
||||
root, _ = state.Commit(true, false)
|
||||
root, _ = state.Commit(0, true, false)
|
||||
state, _ = NewWithSnapshot(root, state.db, state.snap)
|
||||
|
||||
if state.getStateObject(addr) != nil {
|
||||
@@ -731,7 +769,7 @@ func TestMissingTrieNodes(t *testing.T) {
|
||||
a2 := common.BytesToAddress([]byte("another"))
|
||||
state.SetBalance(a2, big.NewInt(100))
|
||||
state.SetCode(a2, []byte{1, 2, 4})
|
||||
root, _ = state.Commit(false, false)
|
||||
root, _ = state.Commit(0, false, false)
|
||||
t.Logf("root: %x", root)
|
||||
// force-flush
|
||||
state.Database().TrieDB().Cap(0)
|
||||
@@ -755,7 +793,7 @@ func TestMissingTrieNodes(t *testing.T) {
|
||||
}
|
||||
// Modify the state
|
||||
state.SetBalance(addr, big.NewInt(2))
|
||||
root, err := state.Commit(false, false)
|
||||
root, err := state.Commit(0, false, false)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got root :%x", root)
|
||||
}
|
||||
@@ -950,7 +988,7 @@ func TestFlushOrderDataLoss(t *testing.T) {
|
||||
state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s})
|
||||
}
|
||||
}
|
||||
root, err := state.Commit(false, false)
|
||||
root, err := state.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit state trie: %v", err)
|
||||
}
|
||||
@@ -1008,3 +1046,37 @@ func TestStateDBTransientStorage(t *testing.T) {
|
||||
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetObject(t *testing.T) {
|
||||
var (
|
||||
disk = rawdb.NewMemoryDatabase()
|
||||
tdb = trie.NewDatabase(disk)
|
||||
db = NewDatabaseWithNodeDB(disk, tdb)
|
||||
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, common.Hash{}, types.EmptyRootHash)
|
||||
state, _ = New(types.EmptyRootHash, db, snaps)
|
||||
addr = common.HexToAddress("0x1")
|
||||
slotA = common.HexToHash("0x1")
|
||||
slotB = common.HexToHash("0x2")
|
||||
)
|
||||
// Initialize account with balance and storage in first transaction.
|
||||
state.SetBalance(addr, big.NewInt(1))
|
||||
state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
|
||||
state.IntermediateRoot(true)
|
||||
|
||||
// Reset account and mutate balance and storages
|
||||
state.CreateAccount(addr)
|
||||
state.SetBalance(addr, big.NewInt(2))
|
||||
state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
|
||||
root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}, false)
|
||||
|
||||
// Ensure the original account is wiped properly
|
||||
snap := snaps.Snapshot(root)
|
||||
slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes()))
|
||||
if len(slot) != 0 {
|
||||
t.Fatalf("Unexpected storage slot")
|
||||
}
|
||||
slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes()))
|
||||
if !bytes.Equal(slot, []byte{0x2}) {
|
||||
t.Fatalf("Unexpected storage slot value %v", slot)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/trie"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
@@ -38,7 +39,7 @@ type testAccount struct {
|
||||
func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) {
|
||||
// Create an empty state
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
sdb := NewDatabase(db)
|
||||
sdb := NewDatabaseWithConfig(db, &trie.Config{Preimages: true})
|
||||
state, _ := New(types.EmptyRootHash, sdb, nil)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
@@ -60,13 +61,13 @@ func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) {
|
||||
if i%5 == 0 {
|
||||
for j := byte(0); j < 5; j++ {
|
||||
hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j})
|
||||
obj.SetState(sdb, hash, hash)
|
||||
obj.SetState(hash, hash)
|
||||
}
|
||||
}
|
||||
state.updateStateObject(obj)
|
||||
accounts = append(accounts, acc)
|
||||
}
|
||||
root, _ := state.Commit(false, false)
|
||||
root, _ := state.Commit(0, false, false)
|
||||
|
||||
// Return the generated state
|
||||
return db, sdb, root, accounts
|
||||
|
||||
@@ -424,7 +424,7 @@ func newTrieOrchestrator(sf *subfetcher) *trieOrchestrator {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
base, err = sf.db.OpenStorageTrie(sf.state, sf.owner, sf.root)
|
||||
base, err = sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root)
|
||||
if err != nil {
|
||||
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
|
||||
return nil
|
||||
|
||||
@@ -169,7 +169,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, blockContext
|
||||
return nil, err
|
||||
}
|
||||
// Create a new context to be used in the EVM environment
|
||||
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
|
||||
vmenv := vm.NewEVM(blockContext, vm.TxContext{BlobHashes: tx.BlobHashes()}, statedb, config, cfg)
|
||||
return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64,
|
||||
// (or deconfigure it if it is being disabled.)
|
||||
if activatingConfig.IsDisabled() {
|
||||
log.Info("Disabling precompile", "name", module.ConfigKey)
|
||||
statedb.Suicide(module.Address)
|
||||
statedb.SelfDestruct(module.Address)
|
||||
// Calling Finalise here effectively commits Suicide call and wipes the contract state.
|
||||
// This enables re-configuration of the same contract state in the same block.
|
||||
// Without an immediate Finalise call after the Suicide, a reconfigured precompiled state can be wiped out
|
||||
|
||||
+95
-108
@@ -27,11 +27,13 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"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"
|
||||
@@ -41,45 +43,10 @@ import (
|
||||
"github.com/ava-labs/subnet-evm/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/holiman/uint256"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
cpcfg = *params.TestChainConfig
|
||||
config = &cpcfg
|
||||
signer = types.LatestSigner(config)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
)
|
||||
|
||||
func makeTx(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey)
|
||||
return tx
|
||||
}
|
||||
|
||||
func mkDynamicTx(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: nonce,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Gas: gasLimit,
|
||||
To: &to,
|
||||
Value: big.NewInt(0),
|
||||
}), signer, testKey)
|
||||
return tx
|
||||
}
|
||||
|
||||
func mkDynamicCreationTx(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: nonce,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Gas: gasLimit,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}), signer, testKey)
|
||||
return tx
|
||||
}
|
||||
|
||||
func u64(val uint64) *uint64 { return &val }
|
||||
|
||||
// TestStateProcessorErrors tests the output from the 'core' errors
|
||||
@@ -87,7 +54,57 @@ 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
|
||||
config := &cpcfg
|
||||
config.CancunTime = u64(0)
|
||||
config.FeeConfig.MinBaseFee = big.NewInt(params.TestMaxBaseFee)
|
||||
|
||||
var (
|
||||
signer = types.LatestSigner(config)
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
)
|
||||
var makeTx = func(key *ecdsa.PrivateKey, nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, key)
|
||||
return tx
|
||||
}
|
||||
var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: nonce,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Gas: gasLimit,
|
||||
To: &to,
|
||||
Value: big.NewInt(0),
|
||||
}), signer, key1)
|
||||
return tx
|
||||
}
|
||||
var mkDynamicCreationTx = func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: nonce,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Gas: gasLimit,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}), signer, key1)
|
||||
return tx
|
||||
}
|
||||
var mkBlobTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, hashes []common.Hash) *types.Transaction {
|
||||
tx, err := types.SignTx(types.NewTx(&types.BlobTx{
|
||||
Nonce: nonce,
|
||||
GasTipCap: uint256.MustFromBig(gasTipCap),
|
||||
GasFeeCap: uint256.MustFromBig(gasFeeCap),
|
||||
Gas: gasLimit,
|
||||
To: to,
|
||||
BlobHashes: hashes,
|
||||
Value: new(uint256.Int),
|
||||
}), signer, key1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
{ // Tests against a 'recent' chain definition
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
@@ -101,8 +118,10 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
},
|
||||
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
|
||||
}
|
||||
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false)
|
||||
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false)
|
||||
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
|
||||
)
|
||||
|
||||
defer blockchain.Stop()
|
||||
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
|
||||
tooBigNumber := new(big.Int).Set(bigNumber)
|
||||
@@ -113,32 +132,32 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
}{
|
||||
{ // ErrNonceTooLow
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 1 [0x734d821c990099c6ae42d78072aadd3931c35328cf03ef4cf5b2a4ac9c398522]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1",
|
||||
},
|
||||
{ // ErrNonceTooHigh
|
||||
txs: []*types.Transaction{
|
||||
makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
makeTx(key1, 100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x0df36254cfbef8ed6961b38fc68aecc777177166144c8a56bc8919e23a559bf4]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0",
|
||||
},
|
||||
{ // ErrGasLimitReached
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), 15000001, big.NewInt(225000000000), nil),
|
||||
makeTx(key1, 0, common.Address{}, big.NewInt(0), 15000001, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x1354370681d2ab68247073d889736f8be4a8d87e35956f0c02658d3670803a66]: gas limit reached",
|
||||
},
|
||||
{ // ErrInsufficientFundsForTransfer
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(4000000000000000000), params.TxGas, big.NewInt(225000000000), nil),
|
||||
makeTx(key1, 0, common.Address{}, big.NewInt(4000000000000000000), params.TxGas, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x1632f2bffcce84a5c91dd8ab2016128fccdbcfbe0485d2c67457e1c793c72a4b]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 4000000000000000000 want 4004725000000000000",
|
||||
},
|
||||
{ // ErrInsufficientFunds
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil),
|
||||
makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x4a69690c4b0cd85e64d0d9ea06302455b01e10a83db964d60281739752003440]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 4000000000000000000 want 18900000000000000000000",
|
||||
},
|
||||
@@ -148,13 +167,13 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
// multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment
|
||||
{ // ErrIntrinsicGas
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(225000000000), nil),
|
||||
makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x2fc3e3b5cc26917d413e26983fe189475f47d4f0757e32aaa5561fcb9c9dc432]: intrinsic gas too low: have 20000, want 21000",
|
||||
},
|
||||
{ // ErrGasLimitReached
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas*762, big.NewInt(225000000000), nil),
|
||||
makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas*762, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x76c07cc2b32007eb1a9c3fa066d579a3d77ec4ecb79bbc266624a601d7b08e46]: gas limit reached",
|
||||
},
|
||||
@@ -199,6 +218,24 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
},
|
||||
want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 4000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000",
|
||||
},
|
||||
{ // ErrMaxInitCodeSizeExceeded
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.TestInitialBaseFee), tooBigInitCode[:]),
|
||||
},
|
||||
want: "could not apply tx 0 [0x18a05f40f29ff16d5287f6f88b21c9f3c7fbc268f707251144996294552c4cd6]: max initcode size exceeded: code size 49153 limit 49152",
|
||||
},
|
||||
{ // ErrIntrinsicGas: Not enough gas to cover init code
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.TestInitialBaseFee), make([]byte, 320)),
|
||||
},
|
||||
want: "could not apply tx 0 [0x849278f616d51ab56bba399551317213ce7a10e4d9cbc3d14bb663e50cb7ab99]: intrinsic gas too low: have 54299, want 54300",
|
||||
},
|
||||
{ // ErrBlobFeeCapTooLow
|
||||
txs: []*types.Transaction{
|
||||
mkBlobTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(1), []common.Hash{(common.Hash{1})}),
|
||||
},
|
||||
want: "could not apply tx 0 [0x6c11015985ce82db691d7b2d017acda296db88b811c3c60dc71449c76256c716]: max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 1 baseFee: 225000000000",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(gspec.ToBlock(), dummy.NewCoinbaseFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
@@ -301,69 +338,6 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrMaxInitCodeSizeExceeded, for this we need extra Shanghai (Durango/EIP-3860) enabled.
|
||||
{
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{
|
||||
Config: ¶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),
|
||||
MandatoryNetworkUpgrades: params.MandatoryNetworkUpgrades{
|
||||
SubnetEVMTimestamp: utils.NewUint64(0),
|
||||
DurangoTimestamp: utils.NewUint64(0),
|
||||
},
|
||||
FeeConfig: params.DefaultFeeConfig,
|
||||
},
|
||||
Alloc: GenesisAlloc{
|
||||
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
|
||||
Balance: big.NewInt(1000000000000000000), // 1 ether
|
||||
Nonce: 0,
|
||||
},
|
||||
},
|
||||
GasLimit: params.DefaultFeeConfig.GasLimit.Uint64(),
|
||||
}
|
||||
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false)
|
||||
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
|
||||
smallInitCode = [320]byte{}
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
for i, tt := range []struct {
|
||||
txs []*types.Transaction
|
||||
want string
|
||||
}{
|
||||
{ // ErrMaxInitCodeSizeExceeded
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.TestInitialBaseFee), tooBigInitCode[:]),
|
||||
},
|
||||
want: "could not apply tx 0 [0x18a05f40f29ff16d5287f6f88b21c9f3c7fbc268f707251144996294552c4cd6]: max initcode size exceeded: code size 49153 limit 49152",
|
||||
},
|
||||
{ // ErrIntrinsicGas: Not enough gas to cover init code
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.TestInitialBaseFee), smallInitCode[:]),
|
||||
},
|
||||
want: "could not apply tx 0 [0x849278f616d51ab56bba399551317213ce7a10e4d9cbc3d14bb663e50cb7ab99]: intrinsic gas too low: have 54299, want 54300",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(gspec.ToBlock(), dummy.NewCoinbaseFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
}
|
||||
if have, want := err.Error(), tt.want; have != want {
|
||||
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBadTxAllowListBlock tests the output generated when the
|
||||
@@ -475,6 +449,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
hasher.Write(header.Number.Bytes())
|
||||
var cumulativeGas uint64
|
||||
var nBlobs int
|
||||
for _, tx := range txs {
|
||||
txh := tx.Hash()
|
||||
hasher.Write(txh[:])
|
||||
@@ -483,8 +458,20 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
|
||||
receipt.GasUsed = tx.Gas()
|
||||
receipts = append(receipts, receipt)
|
||||
cumulativeGas += tx.Gas()
|
||||
nBlobs += len(tx.BlobHashes())
|
||||
}
|
||||
header.Root = common.BytesToHash(hasher.Sum(nil))
|
||||
if config.IsCancun(header.Number, header.Time) {
|
||||
var pExcess, pUsed = uint64(0), uint64(0)
|
||||
if parent.ExcessBlobGas() != nil {
|
||||
pExcess = *parent.ExcessBlobGas()
|
||||
pUsed = *parent.BlobGasUsed()
|
||||
}
|
||||
excess := eip4844.CalcExcessBlobGas(pExcess, pUsed)
|
||||
used := uint64(nBlobs * params.BlobTxBlobGasPerBlob)
|
||||
header.ExcessBlobGas = &excess
|
||||
header.BlobGasUsed = &used
|
||||
}
|
||||
// Assemble and return the final block for sealing
|
||||
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
|
||||
}
|
||||
|
||||
+60
-14
@@ -27,10 +27,12 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus/misc/eip4844"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
@@ -184,16 +186,18 @@ func toWordSize(size uint64) uint64 {
|
||||
// A Message contains the data derived from a single transaction that is relevant to state
|
||||
// processing.
|
||||
type Message struct {
|
||||
To *common.Address
|
||||
From common.Address
|
||||
Nonce uint64
|
||||
Value *big.Int
|
||||
GasLimit uint64
|
||||
GasPrice *big.Int
|
||||
GasFeeCap *big.Int
|
||||
GasTipCap *big.Int
|
||||
Data []byte
|
||||
AccessList types.AccessList
|
||||
To *common.Address
|
||||
From common.Address
|
||||
Nonce uint64
|
||||
Value *big.Int
|
||||
GasLimit uint64
|
||||
GasPrice *big.Int
|
||||
GasFeeCap *big.Int
|
||||
GasTipCap *big.Int
|
||||
Data []byte
|
||||
AccessList types.AccessList
|
||||
BlobGasFeeCap *big.Int
|
||||
BlobHashes []common.Hash
|
||||
|
||||
// When SkipAccountChecks is true, the message nonce is not checked against the
|
||||
// account nonce in state. It also disables checking that the sender is an EOA.
|
||||
@@ -214,6 +218,8 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
||||
Data: tx.Data(),
|
||||
AccessList: tx.AccessList(),
|
||||
SkipAccountChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
@@ -287,12 +293,24 @@ func (st *StateTransition) to() common.Address {
|
||||
func (st *StateTransition) buyGas() error {
|
||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
mgval = mgval.Mul(mgval, st.msg.GasPrice)
|
||||
balanceCheck := mgval
|
||||
balanceCheck := new(big.Int).Set(mgval)
|
||||
if st.msg.GasFeeCap != nil {
|
||||
balanceCheck = new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
||||
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
||||
balanceCheck.Add(balanceCheck, st.msg.Value)
|
||||
}
|
||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
||||
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
||||
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
|
||||
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
|
||||
balanceCheck.Add(balanceCheck, blobBalanceCheck)
|
||||
// Pay for blobGasUsed * actual blob fee
|
||||
blobFee := new(big.Int).SetUint64(blobGas)
|
||||
blobFee.Mul(blobFee, eip4844.CalcBlobFee(*st.evm.Context.ExcessBlobGas))
|
||||
mgval.Add(mgval, blobFee)
|
||||
}
|
||||
}
|
||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
@@ -366,6 +384,29 @@ func (st *StateTransition) preCheck() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check the blob version validity
|
||||
if msg.BlobHashes != nil {
|
||||
if len(msg.BlobHashes) == 0 {
|
||||
return errors.New("blob transaction missing blob hashes")
|
||||
}
|
||||
for i, hash := range msg.BlobHashes {
|
||||
if hash[0] != params.BlobTxHashVersion {
|
||||
return fmt.Errorf("blob %d hash version mismatch (have %d, supported %d)",
|
||||
i, hash[0], params.BlobTxHashVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||
if st.blobGasUsed() > 0 {
|
||||
// Check that the user is paying at least the current blob fee
|
||||
blobFee := eip4844.CalcBlobFee(*st.evm.Context.ExcessBlobGas)
|
||||
if st.msg.BlobGasFeeCap.Cmp(blobFee) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrBlobFeeCapTooLow, st.msg.From.Hex(), st.msg.BlobGasFeeCap, blobFee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return st.buyGas()
|
||||
}
|
||||
|
||||
@@ -406,7 +447,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||
var (
|
||||
msg = st.msg
|
||||
sender = vm.AccountRef(msg.From)
|
||||
rules = st.evm.ChainConfig().AvalancheRules(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
contractCreation = msg.To == nil
|
||||
)
|
||||
|
||||
@@ -480,3 +521,8 @@ func (st *StateTransition) refundGas(subnetEVM bool) {
|
||||
func (st *StateTransition) gasUsed() uint64 {
|
||||
return st.initialGas - st.gasRemaining
|
||||
}
|
||||
|
||||
// blobGasUsed returns the amount of blob gas used by the message.
|
||||
func (st *StateTransition) blobGasUsed() uint64 {
|
||||
return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ava-labs/subnet-evm/precompile/contract"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@@ -51,6 +52,10 @@ func BenchmarkTrie(t *testing.B) {
|
||||
|
||||
func stressTestTrieDb(t *testing.B, numContracts int, callsPerBlock int, elements int64, gasTxLimit uint64) func(int, *BlockGen) {
|
||||
require := require.New(t)
|
||||
config := params.TestChainConfig
|
||||
signer := types.LatestSigner(config)
|
||||
testKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
contractAddr := make([]common.Address, numContracts)
|
||||
contractTxs := make([]*types.Transaction, numContracts)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
// (c) 2024, 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 2022 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 blobpool
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Config are the configuration parameters of the blob transaction pool.
|
||||
type Config struct {
|
||||
Datadir string // Data directory containing the currently executable blobs
|
||||
Datacap uint64 // Soft-cap of database storage (hard cap is larger due to overhead)
|
||||
PriceBump uint64 // Minimum price bump percentage to replace an already existing nonce
|
||||
}
|
||||
|
||||
// DefaultConfig contains the default configurations for the transaction pool.
|
||||
var DefaultConfig = Config{
|
||||
Datadir: "blobpool",
|
||||
Datacap: 10 * 1024 * 1024 * 1024,
|
||||
PriceBump: 100, // either have patience or be aggressive, no mushy ground
|
||||
}
|
||||
|
||||
// sanitize checks the provided user configurations and changes anything that's
|
||||
// unreasonable or unworkable.
|
||||
func (config *Config) sanitize() Config {
|
||||
conf := *config
|
||||
if conf.Datacap < 1 {
|
||||
log.Warn("Sanitizing invalid blobpool storage cap", "provided", conf.Datacap, "updated", DefaultConfig.Datacap)
|
||||
conf.Datacap = DefaultConfig.Datacap
|
||||
}
|
||||
if conf.PriceBump < 1 {
|
||||
log.Warn("Sanitizing invalid blobpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump)
|
||||
conf.PriceBump = DefaultConfig.PriceBump
|
||||
}
|
||||
return conf
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// (c) 2024, 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 2023 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 blobpool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/heap"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// evictHeap is a helper data structure to keep track of the cheapest bottleneck
|
||||
// transaction from each account to determine which account to evict from.
|
||||
//
|
||||
// The heap internally tracks a slice of cheapest transactions from each account
|
||||
// and a mapping from addresses to indices for direct removals/udates.
|
||||
//
|
||||
// The goal of the heap is to decide which account has the worst bottleneck to
|
||||
// evict transactions from.
|
||||
type evictHeap struct {
|
||||
metas *map[common.Address][]*blobTxMeta // Pointer to the blob pool's index for price retrievals
|
||||
|
||||
basefeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the base fee
|
||||
blobfeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the blob fee
|
||||
|
||||
addrs []common.Address // Heap of addresses to retrieve the cheapest out of
|
||||
index map[common.Address]int // Indices into the heap for replacements
|
||||
}
|
||||
|
||||
// newPriceHeap creates a new heap of cheapets accounts in the blob pool to evict
|
||||
// from in case of over saturation.
|
||||
func newPriceHeap(basefee *uint256.Int, blobfee *uint256.Int, index *map[common.Address][]*blobTxMeta) *evictHeap {
|
||||
heap := &evictHeap{
|
||||
metas: index,
|
||||
index: make(map[common.Address]int),
|
||||
}
|
||||
// Populate the heap in account sort order. Not really needed in practice,
|
||||
// but it makes the heap initialization deterministic and less annoying to
|
||||
// test in unit tests.
|
||||
addrs := make([]common.Address, 0, len(*index))
|
||||
for addr := range *index {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
sort.Slice(addrs, func(i, j int) bool { return bytes.Compare(addrs[i][:], addrs[j][:]) < 0 })
|
||||
|
||||
for _, addr := range addrs {
|
||||
heap.index[addr] = len(heap.addrs)
|
||||
heap.addrs = append(heap.addrs, addr)
|
||||
}
|
||||
heap.reinit(basefee, blobfee, true)
|
||||
return heap
|
||||
}
|
||||
|
||||
// reinit updates the pre-calculated dynamic fee jumps in the price heap and runs
|
||||
// the sorting algorithm from scratch on the entire heap.
|
||||
func (h *evictHeap) reinit(basefee *uint256.Int, blobfee *uint256.Int, force bool) {
|
||||
// If the update is mostly the same as the old, don't sort pointlessly
|
||||
basefeeJumps := dynamicFeeJumps(basefee)
|
||||
blobfeeJumps := dynamicFeeJumps(blobfee)
|
||||
|
||||
if !force && math.Abs(h.basefeeJumps-basefeeJumps) < 0.01 && math.Abs(h.blobfeeJumps-blobfeeJumps) < 0.01 { // TODO(karalabe): 0.01 enough, maybe should be smaller? Maybe this optimization is moot?
|
||||
return
|
||||
}
|
||||
// One or both of the dynamic fees jumped, resort the pool
|
||||
h.basefeeJumps = basefeeJumps
|
||||
h.blobfeeJumps = blobfeeJumps
|
||||
|
||||
heap.Init(h)
|
||||
}
|
||||
|
||||
// Len implements sort.Interface as part of heap.Interface, returning the number
|
||||
// of accounts in the pool which can be considered for eviction.
|
||||
func (h *evictHeap) Len() int {
|
||||
return len(h.addrs)
|
||||
}
|
||||
|
||||
// Less implements sort.Interface as part of heap.Interface, returning which of
|
||||
// the two requested accounts has a cheaper bottleneck.
|
||||
func (h *evictHeap) Less(i, j int) bool {
|
||||
txsI := (*(h.metas))[h.addrs[i]]
|
||||
txsJ := (*(h.metas))[h.addrs[j]]
|
||||
|
||||
lastI := txsI[len(txsI)-1]
|
||||
lastJ := txsJ[len(txsJ)-1]
|
||||
|
||||
prioI := evictionPriority(h.basefeeJumps, lastI.evictionExecFeeJumps, h.blobfeeJumps, lastI.evictionBlobFeeJumps)
|
||||
if prioI > 0 {
|
||||
prioI = 0
|
||||
}
|
||||
prioJ := evictionPriority(h.basefeeJumps, lastJ.evictionExecFeeJumps, h.blobfeeJumps, lastJ.evictionBlobFeeJumps)
|
||||
if prioJ > 0 {
|
||||
prioJ = 0
|
||||
}
|
||||
if prioI == prioJ {
|
||||
return lastI.evictionExecTip.Lt(lastJ.evictionExecTip)
|
||||
}
|
||||
return prioI < prioJ
|
||||
}
|
||||
|
||||
// Swap implements sort.Interface as part of heap.Interface, maintaining both the
|
||||
// order of the accounts according to the heap, and the account->item slot mapping
|
||||
// for replacements.
|
||||
func (h *evictHeap) Swap(i, j int) {
|
||||
h.index[h.addrs[i]], h.index[h.addrs[j]] = h.index[h.addrs[j]], h.index[h.addrs[i]]
|
||||
h.addrs[i], h.addrs[j] = h.addrs[j], h.addrs[i]
|
||||
}
|
||||
|
||||
// Push implements heap.Interface, appending an item to the end of the account
|
||||
// ordering as well as the address to item slot mapping.
|
||||
func (h *evictHeap) Push(x any) {
|
||||
h.index[x.(common.Address)] = len(h.addrs)
|
||||
h.addrs = append(h.addrs, x.(common.Address))
|
||||
}
|
||||
|
||||
// Pop implements heap.Interface, removing and returning the last element of the
|
||||
// heap.
|
||||
//
|
||||
// Note, use `heap.Pop`, not `evictHeap.Pop`. This method is used by Go's heap,
|
||||
// to provide the functionality, it does not embed it.
|
||||
func (h *evictHeap) Pop() any {
|
||||
// Remove the last element from the heap
|
||||
size := len(h.addrs)
|
||||
addr := h.addrs[size-1]
|
||||
h.addrs = h.addrs[:size-1]
|
||||
|
||||
// Unindex the removed element and return
|
||||
delete(h.index, addr)
|
||||
return addr
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
// (c) 2024, 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 2023 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 blobpool
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
mrand "math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var rand = mrand.New(mrand.NewSource(1))
|
||||
|
||||
// verifyHeapInternals verifies that all accounts present in the index are also
|
||||
// present in the heap and internals are consistent across various indices.
|
||||
func verifyHeapInternals(t *testing.T, evict *evictHeap) {
|
||||
t.Helper()
|
||||
|
||||
// Ensure that all accounts are present in the heap and no extras
|
||||
seen := make(map[common.Address]struct{})
|
||||
for i, addr := range evict.addrs {
|
||||
seen[addr] = struct{}{}
|
||||
if _, ok := (*evict.metas)[addr]; !ok {
|
||||
t.Errorf("heap contains unexpected address at slot %d: %v", i, addr)
|
||||
}
|
||||
}
|
||||
for addr := range *evict.metas {
|
||||
if _, ok := seen[addr]; !ok {
|
||||
t.Errorf("heap is missing required address %v", addr)
|
||||
}
|
||||
}
|
||||
if len(evict.addrs) != len(*evict.metas) {
|
||||
t.Errorf("heap size %d mismatches metadata size %d", len(evict.addrs), len(*evict.metas))
|
||||
}
|
||||
// Ensure that all accounts are present in the heap order index and no extras
|
||||
have := make([]common.Address, len(evict.index))
|
||||
for addr, i := range evict.index {
|
||||
have[i] = addr
|
||||
}
|
||||
if len(have) != len(evict.addrs) {
|
||||
t.Errorf("heap index size %d mismatches heap size %d", len(have), len(evict.addrs))
|
||||
}
|
||||
for i := 0; i < len(have) && i < len(evict.addrs); i++ {
|
||||
if have[i] != evict.addrs[i] {
|
||||
t.Errorf("heap index for slot %d mismatches: have %v, want %v", i, have[i], evict.addrs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the price heap can correctly sort its set of transactions based on
|
||||
// an input base- and blob fee.
|
||||
func TestPriceHeapSorting(t *testing.T) {
|
||||
tests := []struct {
|
||||
execTips []uint64
|
||||
execFees []uint64
|
||||
blobFees []uint64
|
||||
|
||||
basefee uint64
|
||||
blobfee uint64
|
||||
|
||||
order []int
|
||||
}{
|
||||
// If everything is above the basefee and blobfee, order by miner tip
|
||||
{
|
||||
execTips: []uint64{1, 0, 2},
|
||||
execFees: []uint64{1, 2, 3},
|
||||
blobFees: []uint64{3, 2, 1},
|
||||
basefee: 0,
|
||||
blobfee: 0,
|
||||
order: []int{1, 0, 2},
|
||||
},
|
||||
// If only basefees are used (blob fee matches with network), return the
|
||||
// ones the furthest below the current basefee, splitting same ones with
|
||||
// the tip. Anything above the basefee should be split by tip.
|
||||
{
|
||||
execTips: []uint64{100, 50, 100, 50, 1, 2, 3},
|
||||
execFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000},
|
||||
blobFees: []uint64{0, 0, 0, 0, 0, 0, 0},
|
||||
basefee: 1999,
|
||||
blobfee: 0,
|
||||
order: []int{3, 2, 1, 0, 4, 5, 6},
|
||||
},
|
||||
// If only blobfees are used (base fee matches with network), return the
|
||||
// ones the furthest below the current blobfee, splitting same ones with
|
||||
// the tip. Anything above the blobfee should be split by tip.
|
||||
{
|
||||
execTips: []uint64{100, 50, 100, 50, 1, 2, 3},
|
||||
execFees: []uint64{0, 0, 0, 0, 0, 0, 0},
|
||||
blobFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000},
|
||||
basefee: 0,
|
||||
blobfee: 1999,
|
||||
order: []int{3, 2, 1, 0, 4, 5, 6},
|
||||
},
|
||||
// If both basefee and blobfee is specified, sort by the larger distance
|
||||
// of the two from the current network conditions, splitting same (loglog)
|
||||
// ones via the tip.
|
||||
//
|
||||
// Basefee: 1000
|
||||
// Blobfee: 100
|
||||
//
|
||||
// Tx #0: (800, 80) - 2 jumps below both => priority -1
|
||||
// Tx #1: (630, 63) - 4 jumps below both => priority -2
|
||||
// Tx #2: (800, 63) - 2 jumps below basefee, 4 jumps below blobfee => priority -2 (blob penalty dominates)
|
||||
// Tx #3: (630, 80) - 4 jumps below basefee, 2 jumps below blobfee => priority -2 (base penalty dominates)
|
||||
//
|
||||
// Txs 1, 2, 3 share the same priority, split via tip, prefer 0 as the best
|
||||
{
|
||||
execTips: []uint64{1, 2, 3, 4},
|
||||
execFees: []uint64{800, 630, 800, 630},
|
||||
blobFees: []uint64{80, 63, 63, 80},
|
||||
basefee: 1000,
|
||||
blobfee: 100,
|
||||
order: []int{1, 2, 3, 0},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
// Create an index of the transactions
|
||||
index := make(map[common.Address][]*blobTxMeta)
|
||||
for j := byte(0); j < byte(len(tt.execTips)); j++ {
|
||||
addr := common.Address{j}
|
||||
|
||||
var (
|
||||
execTip = uint256.NewInt(tt.execTips[j])
|
||||
execFee = uint256.NewInt(tt.execFees[j])
|
||||
blobFee = uint256.NewInt(tt.blobFees[j])
|
||||
|
||||
basefeeJumps = dynamicFeeJumps(execFee)
|
||||
blobfeeJumps = dynamicFeeJumps(blobFee)
|
||||
)
|
||||
index[addr] = []*blobTxMeta{{
|
||||
id: uint64(j),
|
||||
size: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
blobFeeCap: blobFee,
|
||||
basefeeJumps: basefeeJumps,
|
||||
blobfeeJumps: blobfeeJumps,
|
||||
evictionExecTip: execTip,
|
||||
evictionExecFeeJumps: basefeeJumps,
|
||||
evictionBlobFeeJumps: blobfeeJumps,
|
||||
}}
|
||||
}
|
||||
// Create a price heap and check the pop order
|
||||
priceheap := newPriceHeap(uint256.NewInt(tt.basefee), uint256.NewInt(tt.blobfee), &index)
|
||||
verifyHeapInternals(t, priceheap)
|
||||
|
||||
for j := 0; j < len(tt.order); j++ {
|
||||
if next := heap.Pop(priceheap); int(next.(common.Address)[0]) != tt.order[j] {
|
||||
t.Errorf("test %d, item %d: order mismatch: have %d, want %d", i, j, next.(common.Address)[0], tt.order[j])
|
||||
} else {
|
||||
delete(index, next.(common.Address)) // remove to simulate a correct pool for the test
|
||||
}
|
||||
verifyHeapInternals(t, priceheap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks reheaping the entire set of accounts in the blob pool.
|
||||
func BenchmarkPriceHeapReinit1MB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024) }
|
||||
func BenchmarkPriceHeapReinit10MB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024) }
|
||||
func BenchmarkPriceHeapReinit100MB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024) }
|
||||
func BenchmarkPriceHeapReinit1GB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024*1024) }
|
||||
func BenchmarkPriceHeapReinit10GB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024*1024) }
|
||||
func BenchmarkPriceHeapReinit25GB(b *testing.B) { benchmarkPriceHeapReinit(b, 25*1024*1024*1024) }
|
||||
func BenchmarkPriceHeapReinit50GB(b *testing.B) { benchmarkPriceHeapReinit(b, 50*1024*1024*1024) }
|
||||
func BenchmarkPriceHeapReinit100GB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024*1024) }
|
||||
|
||||
func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
|
||||
// Calculate how many unique transactions we can fit into the provided disk
|
||||
// data cap
|
||||
blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob)
|
||||
|
||||
// Create a random set of transactions with random fees. Use a separate account
|
||||
// for each transaction to make it worse case.
|
||||
index := make(map[common.Address][]*blobTxMeta)
|
||||
for i := 0; i < int(blobs); i++ {
|
||||
var addr common.Address
|
||||
rand.Read(addr[:])
|
||||
|
||||
var (
|
||||
execTip = uint256.NewInt(rand.Uint64())
|
||||
execFee = uint256.NewInt(rand.Uint64())
|
||||
blobFee = uint256.NewInt(rand.Uint64())
|
||||
|
||||
basefeeJumps = dynamicFeeJumps(execFee)
|
||||
blobfeeJumps = dynamicFeeJumps(blobFee)
|
||||
)
|
||||
index[addr] = []*blobTxMeta{{
|
||||
id: uint64(i),
|
||||
size: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
blobFeeCap: blobFee,
|
||||
basefeeJumps: basefeeJumps,
|
||||
blobfeeJumps: blobfeeJumps,
|
||||
evictionExecTip: execTip,
|
||||
evictionExecFeeJumps: basefeeJumps,
|
||||
evictionBlobFeeJumps: blobfeeJumps,
|
||||
}}
|
||||
}
|
||||
// Create a price heap and reinit it over and over
|
||||
heap := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index)
|
||||
|
||||
basefees := make([]*uint256.Int, b.N)
|
||||
blobfees := make([]*uint256.Int, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
basefees[i] = uint256.NewInt(rand.Uint64())
|
||||
blobfees[i] = uint256.NewInt(rand.Uint64())
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
heap.reinit(basefees[i], blobfees[i], true)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks overflowing the heap over and over (add and then drop).
|
||||
func BenchmarkPriceHeapOverflow1MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024) }
|
||||
func BenchmarkPriceHeapOverflow10MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024) }
|
||||
func BenchmarkPriceHeapOverflow100MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024) }
|
||||
func BenchmarkPriceHeapOverflow1GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024*1024) }
|
||||
func BenchmarkPriceHeapOverflow10GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024*1024) }
|
||||
func BenchmarkPriceHeapOverflow25GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 25*1024*1024*1024) }
|
||||
func BenchmarkPriceHeapOverflow50GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 50*1024*1024*1024) }
|
||||
func BenchmarkPriceHeapOverflow100GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024*1024) }
|
||||
|
||||
func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
|
||||
// Calculate how many unique transactions we can fit into the provided disk
|
||||
// data cap
|
||||
blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob)
|
||||
|
||||
// Create a random set of transactions with random fees. Use a separate account
|
||||
// for each transaction to make it worse case.
|
||||
index := make(map[common.Address][]*blobTxMeta)
|
||||
for i := 0; i < int(blobs); i++ {
|
||||
var addr common.Address
|
||||
rand.Read(addr[:])
|
||||
|
||||
var (
|
||||
execTip = uint256.NewInt(rand.Uint64())
|
||||
execFee = uint256.NewInt(rand.Uint64())
|
||||
blobFee = uint256.NewInt(rand.Uint64())
|
||||
|
||||
basefeeJumps = dynamicFeeJumps(execFee)
|
||||
blobfeeJumps = dynamicFeeJumps(blobFee)
|
||||
)
|
||||
index[addr] = []*blobTxMeta{{
|
||||
id: uint64(i),
|
||||
size: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
blobFeeCap: blobFee,
|
||||
basefeeJumps: basefeeJumps,
|
||||
blobfeeJumps: blobfeeJumps,
|
||||
evictionExecTip: execTip,
|
||||
evictionExecFeeJumps: basefeeJumps,
|
||||
evictionBlobFeeJumps: blobfeeJumps,
|
||||
}}
|
||||
}
|
||||
// Create a price heap and overflow it over and over
|
||||
evict := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index)
|
||||
var (
|
||||
addrs = make([]common.Address, b.N)
|
||||
metas = make([]*blobTxMeta, b.N)
|
||||
)
|
||||
for i := 0; i < b.N; i++ {
|
||||
rand.Read(addrs[i][:])
|
||||
|
||||
var (
|
||||
execTip = uint256.NewInt(rand.Uint64())
|
||||
execFee = uint256.NewInt(rand.Uint64())
|
||||
blobFee = uint256.NewInt(rand.Uint64())
|
||||
|
||||
basefeeJumps = dynamicFeeJumps(execFee)
|
||||
blobfeeJumps = dynamicFeeJumps(blobFee)
|
||||
)
|
||||
metas[i] = &blobTxMeta{
|
||||
id: uint64(int(blobs) + i),
|
||||
size: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
blobFeeCap: blobFee,
|
||||
basefeeJumps: basefeeJumps,
|
||||
blobfeeJumps: blobfeeJumps,
|
||||
evictionExecTip: execTip,
|
||||
evictionExecFeeJumps: basefeeJumps,
|
||||
evictionBlobFeeJumps: blobfeeJumps,
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
index[addrs[i]] = []*blobTxMeta{metas[i]}
|
||||
heap.Push(evict, addrs[i])
|
||||
|
||||
drop := heap.Pop(evict)
|
||||
delete(index, drop.(common.Address))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// (c) 2024, 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 2023 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 blobpool
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/commontype"
|
||||
"github.com/ava-labs/subnet-evm/core/state"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// BlockChain defines the minimal set of methods needed to back a blob pool with
|
||||
// a chain. Exists to allow mocking the live chain out of tests.
|
||||
type BlockChain interface {
|
||||
// Config retrieves the chain's fork configuration.
|
||||
Config() *params.ChainConfig
|
||||
|
||||
// CurrentBlock returns the current head of the chain.
|
||||
CurrentBlock() *types.Header
|
||||
|
||||
// CurrentFinalBlock returns the current block below which blobs should not
|
||||
// be maintained anymore for reorg purposes.
|
||||
CurrentFinalBlock() *types.Header
|
||||
|
||||
// GetBlock retrieves a specific block, used during pool resets.
|
||||
GetBlock(hash common.Hash, number uint64) *types.Block
|
||||
|
||||
// StateAt returns a state database for a given root hash (generally the head).
|
||||
StateAt(root common.Hash) (*state.StateDB, error)
|
||||
|
||||
GetFeeConfigAt(header *types.Header) (commontype.FeeConfig, *big.Int, error)
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// (c) 2024, 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 2023 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 blobpool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/billy"
|
||||
)
|
||||
|
||||
// limboBlob is a wrapper around an opaque blobset that also contains the tx hash
|
||||
// to which it belongs as well as the block number in which it was included for
|
||||
// finality eviction.
|
||||
type limboBlob struct {
|
||||
Owner common.Hash // Owner transaction's hash to support resurrecting reorged txs
|
||||
Block uint64 // Block in which the blob transaction was included
|
||||
|
||||
Blobs []kzg4844.Blob // The opaque blobs originally part of the transaction
|
||||
Commits []kzg4844.Commitment // The commitments for the original blobs
|
||||
Proofs []kzg4844.Proof // The proofs verifying the commitments
|
||||
}
|
||||
|
||||
// limbo is a light, indexed database to temporarily store recently included
|
||||
// blobs until they are finalized. The purpose is to support small reorgs, which
|
||||
// would require pulling back up old blobs (which aren't part of the chain).
|
||||
//
|
||||
// TODO(karalabe): Currently updating the inclusion block of a blob needs a full db rewrite. Can we do without?
|
||||
type limbo struct {
|
||||
store billy.Database // Persistent data store for limboed blobs
|
||||
|
||||
index map[common.Hash]uint64 // Mappings from tx hashes to datastore ids
|
||||
groups map[uint64]map[uint64]common.Hash // Set of txs included in past blocks
|
||||
}
|
||||
|
||||
// newLimbo opens and indexes a set of limboed blob transactions.
|
||||
func newLimbo(datadir string) (*limbo, error) {
|
||||
l := &limbo{
|
||||
index: make(map[common.Hash]uint64),
|
||||
groups: make(map[uint64]map[uint64]common.Hash),
|
||||
}
|
||||
// Index all limboed blobs on disk and delete anything inprocessable
|
||||
var fails []uint64
|
||||
index := func(id uint64, size uint32, data []byte) {
|
||||
if l.parseBlob(id, data) != nil {
|
||||
fails = append(fails, id)
|
||||
}
|
||||
}
|
||||
store, err := billy.Open(billy.Options{Path: datadir}, newSlotter(), index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.store = store
|
||||
|
||||
if len(fails) > 0 {
|
||||
log.Warn("Dropping invalidated limboed blobs", "ids", fails)
|
||||
for _, id := range fails {
|
||||
if err := l.store.Delete(id); err != nil {
|
||||
l.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Close closes down the underlying persistent store.
|
||||
func (l *limbo) Close() error {
|
||||
return l.store.Close()
|
||||
}
|
||||
|
||||
// parseBlob is a callback method on limbo creation that gets called for each
|
||||
// limboed blob on disk to create the in-memory metadata index.
|
||||
func (l *limbo) parseBlob(id uint64, data []byte) error {
|
||||
item := new(limboBlob)
|
||||
if err := rlp.DecodeBytes(data, item); err != nil {
|
||||
// This path is impossible unless the disk data representation changes
|
||||
// across restarts. For that ever unprobable case, recover gracefully
|
||||
// by ignoring this data entry.
|
||||
log.Error("Failed to decode blob limbo entry", "id", id, "err", err)
|
||||
return err
|
||||
}
|
||||
if _, ok := l.index[item.Owner]; ok {
|
||||
// This path is impossible, unless due to a programming error a blob gets
|
||||
// inserted into the limbo which was already part of if. Recover gracefully
|
||||
// by ignoring this data entry.
|
||||
log.Error("Dropping duplicate blob limbo entry", "owner", item.Owner, "id", id)
|
||||
return errors.New("duplicate blob")
|
||||
}
|
||||
l.index[item.Owner] = id
|
||||
|
||||
if _, ok := l.groups[item.Block]; !ok {
|
||||
l.groups[item.Block] = make(map[uint64]common.Hash)
|
||||
}
|
||||
l.groups[item.Block][id] = item.Owner
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// finalize evicts all blobs belonging to a recently finalized block or older.
|
||||
func (l *limbo) finalize(final *types.Header) {
|
||||
// Just in case there's no final block yet (network not yet merged, weird
|
||||
// restart, sethead, etc), fail gracefully.
|
||||
if final == nil {
|
||||
log.Error("Nil finalized block cannot evict old blobs")
|
||||
return
|
||||
}
|
||||
for block, ids := range l.groups {
|
||||
if block > final.Number.Uint64() {
|
||||
continue
|
||||
}
|
||||
for id, owner := range ids {
|
||||
if err := l.store.Delete(id); err != nil {
|
||||
log.Error("Failed to drop finalized blob", "block", block, "id", id, "err", err)
|
||||
}
|
||||
delete(l.index, owner)
|
||||
}
|
||||
delete(l.groups, block)
|
||||
}
|
||||
}
|
||||
|
||||
// push stores a new blob transaction into the limbo, waiting until finality for
|
||||
// it to be automatically evicted.
|
||||
func (l *limbo) push(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
|
||||
// If the blobs are already tracked by the limbo, consider it a programming
|
||||
// error. There's not much to do against it, but be loud.
|
||||
if _, ok := l.index[tx]; ok {
|
||||
log.Error("Limbo cannot push already tracked blobs", "tx", tx)
|
||||
return errors.New("already tracked blob transaction")
|
||||
}
|
||||
if err := l.setAndIndex(tx, block, blobs, commits, proofs); err != nil {
|
||||
log.Error("Failed to set and index liboed blobs", "tx", tx, "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pull retrieves a previously pushed set of blobs back from the limbo, removing
|
||||
// it at the same time. This method should be used when a previously included blob
|
||||
// transaction gets reorged out.
|
||||
func (l *limbo) pull(tx common.Hash) ([]kzg4844.Blob, []kzg4844.Commitment, []kzg4844.Proof, error) {
|
||||
// If the blobs are not tracked by the limbo, there's not much to do. This
|
||||
// can happen for example if a blob transaction is mined without pushing it
|
||||
// into the network first.
|
||||
id, ok := l.index[tx]
|
||||
if !ok {
|
||||
log.Trace("Limbo cannot pull non-tracked blobs", "tx", tx)
|
||||
return nil, nil, nil, errors.New("unseen blob transaction")
|
||||
}
|
||||
item, err := l.getAndDrop(id)
|
||||
if err != nil {
|
||||
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return item.Blobs, item.Commits, item.Proofs, nil
|
||||
}
|
||||
|
||||
// update changes the block number under which a blob transaction is tracked. This
|
||||
// method should be used when a reorg changes a transaction's inclusion block.
|
||||
//
|
||||
// The method may log errors for various unexpcted scenarios but will not return
|
||||
// any of it since there's no clear error case. Some errors may be due to coding
|
||||
// issues, others caused by signers mining MEV stuff or swapping transactions. In
|
||||
// all cases, the pool needs to continue operating.
|
||||
func (l *limbo) update(tx common.Hash, block uint64) {
|
||||
// If the blobs are not tracked by the limbo, there's not much to do. This
|
||||
// can happen for example if a blob transaction is mined without pushing it
|
||||
// into the network first.
|
||||
id, ok := l.index[tx]
|
||||
if !ok {
|
||||
log.Trace("Limbo cannot update non-tracked blobs", "tx", tx)
|
||||
return
|
||||
}
|
||||
// If there was no change in the blob's inclusion block, don't mess around
|
||||
// with heavy database operations.
|
||||
if _, ok := l.groups[block][id]; ok {
|
||||
log.Trace("Blob transaction unchanged in limbo", "tx", tx, "block", block)
|
||||
return
|
||||
}
|
||||
// Retrieve the old blobs from the data store and write tehm back with a new
|
||||
// block number. IF anything fails, there's not much to do, go on.
|
||||
item, err := l.getAndDrop(id)
|
||||
if err != nil {
|
||||
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
if err := l.setAndIndex(tx, block, item.Blobs, item.Commits, item.Proofs); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Blob transaction updated in limbo", "tx", tx, "old-block", item.Block, "new-block", block)
|
||||
}
|
||||
|
||||
// getAndDrop retrieves a blob item from the limbo store and deletes it both from
|
||||
// the store and indices.
|
||||
func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
|
||||
data, err := l.store.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item := new(limboBlob)
|
||||
if err = rlp.DecodeBytes(data, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(l.index, item.Owner)
|
||||
delete(l.groups[item.Block], id)
|
||||
if len(l.groups[item.Block]) == 0 {
|
||||
delete(l.groups, item.Block)
|
||||
}
|
||||
if err := l.store.Delete(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// setAndIndex assembles a limbo blob database entry and stores it, also updating
|
||||
// the in-memory indices.
|
||||
func (l *limbo) setAndIndex(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
|
||||
item := &limboBlob{
|
||||
Owner: tx,
|
||||
Block: block,
|
||||
Blobs: blobs,
|
||||
Commits: commits,
|
||||
Proofs: proofs,
|
||||
}
|
||||
data, err := rlp.EncodeToBytes(item)
|
||||
if err != nil {
|
||||
panic(err) // cannot happen runtime, dev error
|
||||
}
|
||||
id, err := l.store.Put(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.index[tx] = id
|
||||
if _, ok := l.groups[block]; !ok {
|
||||
l.groups[block] = make(map[uint64]common.Hash)
|
||||
}
|
||||
l.groups[block][id] = tx
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// (c) 2024, 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 2023 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 blobpool
|
||||
|
||||
import "github.com/ava-labs/subnet-evm/metrics"
|
||||
|
||||
var (
|
||||
// datacapGauge tracks the user's configured capacity for the blob pool. It
|
||||
// is mostly a way to expose/debug issues.
|
||||
datacapGauge = metrics.NewRegisteredGauge("blobpool/datacap", nil)
|
||||
|
||||
// The below metrics track the per-datastore metrics for the primary blob
|
||||
// store and the temporary limbo store.
|
||||
datausedGauge = metrics.NewRegisteredGauge("blobpool/dataused", nil)
|
||||
datarealGauge = metrics.NewRegisteredGauge("blobpool/datareal", nil)
|
||||
slotusedGauge = metrics.NewRegisteredGauge("blobpool/slotused", nil)
|
||||
|
||||
limboDatausedGauge = metrics.NewRegisteredGauge("blobpool/limbo/dataused", nil)
|
||||
limboDatarealGauge = metrics.NewRegisteredGauge("blobpool/limbo/datareal", nil)
|
||||
limboSlotusedGauge = metrics.NewRegisteredGauge("blobpool/limbo/slotused", nil)
|
||||
|
||||
// The below metrics track the per-shelf metrics for the primary blob store
|
||||
// and the temporary limbo store.
|
||||
shelfDatausedGaugeName = "blobpool/shelf_%d/dataused"
|
||||
shelfDatagapsGaugeName = "blobpool/shelf_%d/datagaps"
|
||||
shelfSlotusedGaugeName = "blobpool/shelf_%d/slotused"
|
||||
shelfSlotgapsGaugeName = "blobpool/shelf_%d/slotgaps"
|
||||
|
||||
limboShelfDatausedGaugeName = "blobpool/limbo/shelf_%d/dataused"
|
||||
limboShelfDatagapsGaugeName = "blobpool/limbo/shelf_%d/datagaps"
|
||||
limboShelfSlotusedGaugeName = "blobpool/limbo/shelf_%d/slotused"
|
||||
limboShelfSlotgapsGaugeName = "blobpool/limbo/shelf_%d/slotgaps"
|
||||
|
||||
// The oversized metrics aggregate the shelf stats above the max blob count
|
||||
// limits to track transactions that are just huge, but don't contain blobs.
|
||||
//
|
||||
// There are no oversized data in the limbo, it only contains blobs and some
|
||||
// constant metadata.
|
||||
oversizedDatausedGauge = metrics.NewRegisteredGauge("blobpool/oversized/dataused", nil)
|
||||
oversizedDatagapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/datagaps", nil)
|
||||
oversizedSlotusedGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotused", nil)
|
||||
oversizedSlotgapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotgaps", nil)
|
||||
|
||||
// basefeeGauge and blobfeeGauge track the current network 1559 base fee and
|
||||
// 4844 blob fee respectively.
|
||||
basefeeGauge = metrics.NewRegisteredGauge("blobpool/basefee", nil)
|
||||
blobfeeGauge = metrics.NewRegisteredGauge("blobpool/blobfee", nil)
|
||||
|
||||
// pooltipGauge is the configurable miner tip to permit a transaction into
|
||||
// the pool.
|
||||
pooltipGauge = metrics.NewRegisteredGauge("blobpool/pooltip", nil)
|
||||
|
||||
// addwait/time, resetwait/time and getwait/time track the rough health of
|
||||
// the pool and wether or not it's capable of keeping up with the load from
|
||||
// the network.
|
||||
addwaitHist = metrics.NewRegisteredHistogram("blobpool/addwait", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
addtimeHist = metrics.NewRegisteredHistogram("blobpool/addtime", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
getwaitHist = metrics.NewRegisteredHistogram("blobpool/getwait", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
gettimeHist = metrics.NewRegisteredHistogram("blobpool/gettime", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
pendwaitHist = metrics.NewRegisteredHistogram("blobpool/pendwait", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
pendtimeHist = metrics.NewRegisteredHistogram("blobpool/pendtime", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
resetwaitHist = metrics.NewRegisteredHistogram("blobpool/resetwait", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
resettimeHist = metrics.NewRegisteredHistogram("blobpool/resettime", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
// (c) 2024, 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 2023 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 blobpool
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/bits"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// log2_1_125 is used in the eviction priority calculation.
|
||||
var log2_1_125 = math.Log2(1.125)
|
||||
|
||||
// evictionPriority calculates the eviction priority based on the algorithm
|
||||
// described in the BlobPool docs for a both fee components.
|
||||
//
|
||||
// This method takes about 8ns on a very recent laptop CPU, recalculating about
|
||||
// 125 million transaction priority values per second.
|
||||
func evictionPriority(basefeeJumps float64, txBasefeeJumps, blobfeeJumps, txBlobfeeJumps float64) int {
|
||||
var (
|
||||
basefeePriority = evictionPriority1D(basefeeJumps, txBasefeeJumps)
|
||||
blobfeePriority = evictionPriority1D(blobfeeJumps, txBlobfeeJumps)
|
||||
)
|
||||
if basefeePriority < blobfeePriority {
|
||||
return basefeePriority
|
||||
}
|
||||
return blobfeePriority
|
||||
}
|
||||
|
||||
// evictionPriority1D calculates the eviction priority based on the algorithm
|
||||
// described in the BlobPool docs for a single fee component.
|
||||
func evictionPriority1D(basefeeJumps float64, txfeeJumps float64) int {
|
||||
jumps := txfeeJumps - basefeeJumps
|
||||
if int(jumps) == 0 {
|
||||
return 0 // can't log2 0
|
||||
}
|
||||
if jumps < 0 {
|
||||
return -intLog2(uint(-math.Floor(jumps)))
|
||||
}
|
||||
return intLog2(uint(math.Ceil(jumps)))
|
||||
}
|
||||
|
||||
// dynamicFeeJumps calculates the log1.125(fee), namely the number of fee jumps
|
||||
// needed to reach the requested one. We only use it when calculating the jumps
|
||||
// between 2 fees, so it doesn't matter from what exact number with returns.
|
||||
// it returns the result from (0, 1, 1.125).
|
||||
//
|
||||
// This method is very expensive, taking about 75ns on a very recent laptop CPU,
|
||||
// but the result does not change with the lifetime of a transaction, so it can
|
||||
// be cached.
|
||||
func dynamicFeeJumps(fee *uint256.Int) float64 {
|
||||
if fee.IsZero() {
|
||||
return 0 // can't log2 zero, should never happen outside tests, but don't choke
|
||||
}
|
||||
return math.Log2(fee.Float64()) / log2_1_125
|
||||
}
|
||||
|
||||
// intLog2 is a helper to calculate the integral part of a log2 of an unsigned
|
||||
// integer. It is a very specific calculation that's not particularly useful in
|
||||
// general, but it's what we need here (it's fast).
|
||||
func intLog2(n uint) int {
|
||||
switch {
|
||||
case n == 0:
|
||||
panic("log2(0) is undefined")
|
||||
|
||||
case n < 2048:
|
||||
return bits.UintSize - bits.LeadingZeros(n) - 1
|
||||
|
||||
default:
|
||||
// The input is log1.125(uint256) = log2(uint256) / log2(1.125). At the
|
||||
// most extreme, log2(uint256) will be a bit below 257, and the constant
|
||||
// log2(1.125) ~= 0.17. The larges input thus is ~257 / ~0.17 ~= ~1511.
|
||||
panic("dynamic fee jump diffs cannot reach this")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// (c) 2024, 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 2023 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 blobpool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// Tests that the priority fees are calculated correctly as the log2 of the fee
|
||||
// jumps needed to go from the base fee to the tx's fee cap.
|
||||
func TestPriorityCalculation(t *testing.T) {
|
||||
tests := []struct {
|
||||
basefee uint64
|
||||
txfee uint64
|
||||
result int
|
||||
}{
|
||||
{basefee: 7, txfee: 10, result: 2}, // 3.02 jumps, 4 ceil, 2 log2
|
||||
{basefee: 17_200_000_000, txfee: 17_200_000_000, result: 0}, // 0 jumps, special case 0 log2
|
||||
{basefee: 9_853_941_692, txfee: 11_085_092_510, result: 0}, // 0.99 jumps, 1 ceil, 0 log2
|
||||
{basefee: 11_544_106_391, txfee: 10_356_781_100, result: 0}, // -0.92 jumps, -1 floor, 0 log2
|
||||
{basefee: 17_200_000_000, txfee: 7, result: -7}, // -183.57 jumps, -184 floor, -7 log2
|
||||
{basefee: 7, txfee: 17_200_000_000, result: 7}, // 183.57 jumps, 184 ceil, 7 log2
|
||||
}
|
||||
for i, tt := range tests {
|
||||
var (
|
||||
baseJumps = dynamicFeeJumps(uint256.NewInt(tt.basefee))
|
||||
feeJumps = dynamicFeeJumps(uint256.NewInt(tt.txfee))
|
||||
)
|
||||
if prio := evictionPriority1D(baseJumps, feeJumps); prio != tt.result {
|
||||
t.Errorf("test %d priority mismatch: have %d, want %d", i, prio, tt.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks how many dynamic fee jump values can be done.
|
||||
func BenchmarkDynamicFeeJumpCalculation(b *testing.B) {
|
||||
fees := make([]*uint256.Int, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
fees[i] = uint256.NewInt(rand.Uint64())
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
dynamicFeeJumps(fees[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks how many priority recalculations can be done.
|
||||
func BenchmarkPriorityCalculation(b *testing.B) {
|
||||
// The basefee and blob fee is constant for all transactions across a block,
|
||||
// so we can assume theit absolute jump counts can be pre-computed.
|
||||
basefee := uint256.NewInt(17_200_000_000) // 17.2 Gwei is the 22.03.2023 zero-emission basefee, random number
|
||||
blobfee := uint256.NewInt(123_456_789_000) // Completely random, no idea what this will be
|
||||
|
||||
basefeeJumps := dynamicFeeJumps(basefee)
|
||||
blobfeeJumps := dynamicFeeJumps(blobfee)
|
||||
|
||||
// The transaction's fee cap and blob fee cap are constant across the life
|
||||
// of the transaction, so we can pre-calculate and cache them.
|
||||
txBasefeeJumps := make([]float64, b.N)
|
||||
txBlobfeeJumps := make([]float64, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64()))
|
||||
txBlobfeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64()))
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
evictionPriority(basefeeJumps, txBasefeeJumps[i], blobfeeJumps, txBlobfeeJumps[i])
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// (c) 2019-2020, Ava Labs, Inc.
|
||||
// (c) 2024, Ava Labs, Inc.
|
||||
//
|
||||
// This file is a derived work, based on the go-ethereum library whose original
|
||||
// notices appear below.
|
||||
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Much love to the original authors for their work.
|
||||
// **********
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// Copyright 2023 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
|
||||
@@ -24,23 +24,25 @@
|
||||
// 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 snapshot
|
||||
package blobpool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
// newSlotter creates a helper method for the Billy datastore that returns the
|
||||
// individual shelf sizes used to store transactions in.
|
||||
//
|
||||
// The slotter will create shelves for each possible blob count + some tx metadata
|
||||
// wiggle room, up to the max permitted limits.
|
||||
//
|
||||
// The slotter also creates a shelf for 0-blob transactions. Whilst those are not
|
||||
// allowed in the current protocol, having an empty shelf is not a relevant use
|
||||
// of resources, but it makes stress testing with junk transactions simpler.
|
||||
func newSlotter() func() (uint32, bool) {
|
||||
slotsize := uint32(txAvgSize)
|
||||
slotsize -= uint32(blobSize) // underflows, it's ok, will overflow back in the first return
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
return func() (size uint32, done bool) {
|
||||
slotsize += blobSize
|
||||
finished := slotsize > maxBlobsPerTransaction*blobSize+txMaxSize
|
||||
|
||||
// hashes is a helper to implement sort.Interface.
|
||||
type hashes []common.Hash
|
||||
|
||||
// Len is the number of elements in the collection.
|
||||
func (hs hashes) Len() int { return len(hs) }
|
||||
|
||||
// Less reports whether the element with index i should sort before the element
|
||||
// with index j.
|
||||
func (hs hashes) Less(i, j int) bool { return bytes.Compare(hs[i][:], hs[j][:]) < 0 }
|
||||
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
func (hs hashes) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] }
|
||||
return slotsize, finished
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user