Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge master atomic base #757

Merged
merged 12 commits into from
Jan 20, 2025
Merged
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Coreth and the C-Chain

[Avalanche](https://docs.avax.network/intro) is a network composed of multiple blockchains.
[Avalanche](https://www.avax.network/) is a network composed of multiple blockchains.
Each blockchain is an instance of a Virtual Machine (VM), much like an object in an object-oriented language is an instance of a class.
That is, the VM defines the behavior of the blockchain.
Coreth (from core Ethereum) is the [Virtual Machine (VM)](https://docs.avax.network/learn/virtual-machines) that defines the Contract Chain (C-Chain).
Expand Down Expand Up @@ -39,7 +39,7 @@ Full documentation for the C-Chain's API can be found [here.](https://docs.avax.

## Compatibility

The C-Chain is compatible with almost all Ethereum tooling, including [Core,](https://docs.avax.network/build/dapp/launch-dapp#through-core) [Metamask,](https://docs.avax.network/build/dapp/launch-dapp#through-metamask) [Remix](https://docs.avax.network/build/tutorials/smart-contracts/deploy-a-smart-contract-on-avalanche-using-remix-and-metamask) and [Truffle.](https://docs.avax.network/build/tutorials/smart-contracts/using-truffle-with-the-avalanche-c-chain)
The C-Chain is compatible with almost all Ethereum tooling, including [Core,](https://docs.avax.network/build/dapp/launch-dapp#through-core) [Metamask,](https://docs.avax.network/build/dapp/launch-dapp#through-metamask) [Remix](https://docs.avax.network/dapps/smart-contract-dev/deploy-with-remix-ide) and [Truffle.](https://docs.avax.network/build/tutorials/smart-contracts/using-truffle-with-the-avalanche-c-chain)

## Differences Between Avalanche C-Chain and Ethereum

Expand Down
7 changes: 7 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Release Notes

## Pending Release

## [v0.14.1](https://github.com/ava-labs/coreth/releases/tag/v0.14.1)

- Removed deprecated `ExportKey`, `ExportAVAX`, `Export`, `ImportKey`, `ImportAVAX`, `Import` APIs
- IMPORTANT: `eth_getProof` calls for historical state will be rejected by default.
- On archive nodes (`"pruning-enabled": false`): queries for historical proofs for state older than approximately 24 hours preceding the last accepted block will be rejected by default. This can be adjusted with the new option `historical-proof-query-window` which defines the number of blocks before the last accepted block which should be accepted for state proof queries, or set to `0` to accept any block number state query (previous behavior).
- On `pruning` nodes: queries for proofs past the tip buffer (32 blocks) will be rejected. This is in support of moving to a path based storage scheme, which does not support historical state proofs.
- Remove API eth_getAssetBalance that was used to query ANT balances (deprecated since v0.10.0)
- Remove legacy gossip handler and metrics (deprecated since v0.10.0)
- Refactored trie_prefetcher.go to be structurally similar to [upstream](https://github.com/ethereum/go-ethereum/tree/v1.13.14).
Expand Down
6 changes: 3 additions & 3 deletions core/state_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ func init() {
}

const (
// tipBufferSize is the number of recent accepted tries to keep in the TrieDB
// TipBufferSize is the number of recent accepted tries to keep in the TrieDB
// dirties cache at tip (only applicable in [pruning] mode).
//
// Keeping extra tries around at tip enables clients to query data from
// recent trie roots.
tipBufferSize = 32
TipBufferSize = 32

// flushWindow is the distance to the [commitInterval] when we start
// optimistically flushing trie nodes to disk (only applicable in [pruning]
Expand Down Expand Up @@ -79,7 +79,7 @@ func NewTrieWriter(db TrieDB, config *CacheConfig) TrieWriter {
targetCommitSize: common.StorageSize(config.TrieDirtyCommitTarget) * 1024 * 1024,
imageCap: 4 * 1024 * 1024,
commitInterval: config.CommitInterval,
tipBuffer: NewBoundedBuffer(tipBufferSize, db.Dereference),
tipBuffer: NewBoundedBuffer(TipBufferSize, db.Dereference),
}
cm.flushStepSize = (cm.memoryCap - cm.targetCommitSize) / common.StorageSize(flushWindow)
return cm
Expand Down
6 changes: 3 additions & 3 deletions core/state_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ func TestCappedMemoryTrieWriter(t *testing.T) {
assert.Equal(common.Hash{}, m.LastCommit, "should not have committed block on insert")

w.AcceptTrie(block)
if i <= tipBufferSize {
if i <= TipBufferSize {
assert.Equal(common.Hash{}, m.LastDereference, "should not have dereferenced block on accept")
} else {
assert.Equal(common.BigToHash(big.NewInt(int64(i-tipBufferSize))), m.LastDereference, "should have dereferenced old block on last accept")
assert.Equal(common.BigToHash(big.NewInt(int64(i-TipBufferSize))), m.LastDereference, "should have dereferenced old block on last accept")
m.LastDereference = common.Hash{}
}
if i < int(cacheConfig.CommitInterval) {
Expand All @@ -77,7 +77,7 @@ func TestNoPruningTrieWriter(t *testing.T) {
m := &MockTrieDB{}
w := NewTrieWriter(m, &CacheConfig{})
assert := assert.New(t)
for i := 0; i < tipBufferSize+1; i++ {
for i := 0; i < TipBufferSize+1; i++ {
bigI := big.NewInt(int64(i))
block := types.NewBlock(
&types.Header{
Expand Down
15 changes: 15 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,28 @@ type EthAPIBackend struct {
allowUnfinalizedQueries bool
eth *Ethereum
gpo *gasprice.Oracle

// historicalProofQueryWindow is the number of blocks before the last accepted block to be accepted for
// state queries when running archive mode.
historicalProofQueryWindow uint64
}

// ChainConfig returns the active chain configuration.
func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
return b.eth.blockchain.Config()
}

// IsArchive returns true if the node is running in archive mode, false otherwise.
func (b *EthAPIBackend) IsArchive() bool {
return !b.eth.config.Pruning
}

// HistoricalProofQueryWindow returns the number of blocks before the last accepted block to be accepted for state queries.
// It returns 0 to indicate to accept any block number for state queries.
func (b *EthAPIBackend) HistoricalProofQueryWindow() uint64 {
return b.historicalProofQueryWindow
}

func (b *EthAPIBackend) IsAllowUnfinalizedQueries() bool {
return b.allowUnfinalizedQueries
}
Expand Down
11 changes: 6 additions & 5 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,12 @@ func New(
}

eth.APIBackend = &EthAPIBackend{
extRPCEnabled: stack.Config().ExtRPCEnabled(),
allowUnprotectedTxs: config.AllowUnprotectedTxs,
allowUnprotectedTxHashes: allowUnprotectedTxHashes,
allowUnfinalizedQueries: config.AllowUnfinalizedQueries,
eth: eth,
extRPCEnabled: stack.Config().ExtRPCEnabled(),
allowUnprotectedTxs: config.AllowUnprotectedTxs,
allowUnprotectedTxHashes: allowUnprotectedTxHashes,
allowUnfinalizedQueries: config.AllowUnfinalizedQueries,
historicalProofQueryWindow: config.HistoricalProofQueryWindow,
eth: eth,
}
if config.AllowUnprotectedTxs {
log.Info("Unprotected transactions allowed")
Expand Down
5 changes: 5 additions & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ type Config struct {
// AllowUnfinalizedQueries allow unfinalized queries
AllowUnfinalizedQueries bool

// HistoricalProofQueryWindow is the number of blocks before the last accepted block to be accepted for state queries.
// For archive nodes, it defaults to 43200 and can be set to 0 to indicate to accept any block query.
// For non-archive nodes, it is forcibly set to the value of [core.TipBufferSize].
HistoricalProofQueryWindow uint64

// AllowUnprotectedTxs allow unprotected transactions to be locally issued.
// Unprotected transactions are transactions that are signed without EIP-155
// replay protection.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22.8

require (
github.com/VictoriaMetrics/fastcache v1.12.1
github.com/ava-labs/avalanchego v1.12.2-0.20250106102004-902377d447ba
github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625
github.com/cespare/cp v0.1.0
github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233
github.com/davecgh/go-spew v1.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/ava-labs/avalanchego v1.12.2-0.20250106102004-902377d447ba h1:7t2ORGM53sqdsczNZGFQIK99of9yeetCld90keJ47Os=
github.com/ava-labs/avalanchego v1.12.2-0.20250106102004-902377d447ba/go.mod h1:oK/C7ZGo5cAEayBKBoawh2EpOo3E9gD1rpd9NAM0RkQ=
github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625 h1:sbmfwhpetCKI7Unzw9jJ+2HWLRFM7vq7th0pH2LclCQ=
github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625/go.mod h1:oK/C7ZGo5cAEayBKBoawh2EpOo3E9gD1rpd9NAM0RkQ=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
Expand Down
7 changes: 7 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,14 @@ func (n *proofList) Delete(key []byte) error {
}

// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
// If the requested block is part of historical blocks and the node does not accept
// getting proofs for historical blocks, an error is returned.
func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
err := s.stateQueryBlockNumberAllowed(blockNrOrHash)
if err != nil {
return nil, fmt.Errorf("historical proof query not allowed: %s", err)
}

var (
keys = make([]common.Hash, len(storageKeys))
keyLengths = make([]int, len(storageKeys))
Expand Down
39 changes: 39 additions & 0 deletions internal/ethapi/api_extra.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,42 @@ func (s *BlockChainAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, erro
}
return results, nil
}

// stateQueryBlockNumberAllowed returns a nil error if:
// - the node is configured to accept any state query (the query window is zero)
// - the block given has its number within the query window before the last accepted block.
// This query window is set to [core.TipBufferSize] when running in a non-archive mode.
//
// Otherwise, it returns a non-nil error containing block number information.
func (s *BlockChainAPI) stateQueryBlockNumberAllowed(blockNumOrHash rpc.BlockNumberOrHash) (err error) {
queryWindow := uint64(core.TipBufferSize)
if s.b.IsArchive() {
queryWindow = s.b.HistoricalProofQueryWindow()
if queryWindow == 0 {
return nil
}
}

lastAcceptedNumber := s.b.LastAcceptedBlock().NumberU64()

var number uint64
if blockNumOrHash.BlockNumber != nil {
number = uint64(blockNumOrHash.BlockNumber.Int64())
} else {
block, err := s.b.BlockByNumberOrHash(context.Background(), blockNumOrHash)
if err != nil {
return fmt.Errorf("failed to get block from hash: %s", err)
}
number = block.NumberU64()
}

var oldestAllowed uint64
if lastAcceptedNumber > queryWindow {
oldestAllowed = lastAcceptedNumber - queryWindow
}
if number >= oldestAllowed {
return nil
}
return fmt.Errorf("block number %d is before the oldest allowed block number %d (window of %d blocks)",
number, oldestAllowed, queryWindow)
}
132 changes: 132 additions & 0 deletions internal/ethapi/api_extra_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// (c) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package ethapi

import (
"fmt"
"math/big"
"testing"

"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/rpc"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)

func TestBlockChainAPI_stateQueryBlockNumberAllowed(t *testing.T) {
t.Parallel()

const queryWindow uint64 = 1024

makeBlockWithNumber := func(number uint64) *types.Block {
header := &types.Header{
Number: big.NewInt(int64(number)),
}
return types.NewBlock(header, nil, nil, nil, nil)
}

testCases := map[string]struct {
blockNumOrHash rpc.BlockNumberOrHash
makeBackend func(ctrl *gomock.Controller) *MockBackend
wantErrMessage string
}{
"zero_query_window": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(1000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().IsArchive().Return(true)
backend.EXPECT().HistoricalProofQueryWindow().Return(uint64(0))
return backend
},
},
"block_number_allowed_below_window": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(1000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().IsArchive().Return(true)
backend.EXPECT().HistoricalProofQueryWindow().Return(queryWindow)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(1020))
return backend
},
},
"block_number_allowed": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(2000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().IsArchive().Return(true)
backend.EXPECT().HistoricalProofQueryWindow().Return(queryWindow)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
return backend
},
},
"block_number_allowed_by_hash": {
blockNumOrHash: rpc.BlockNumberOrHashWithHash(common.Hash{99}, false),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().IsArchive().Return(true)
backend.EXPECT().HistoricalProofQueryWindow().Return(queryWindow)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
backend.EXPECT().
BlockByNumberOrHash(gomock.Any(), gomock.Any()).
Return(makeBlockWithNumber(2000), nil)
return backend
},
},
"block_number_allowed_by_hash_error": {
blockNumOrHash: rpc.BlockNumberOrHashWithHash(common.Hash{99}, false),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().IsArchive().Return(true)
backend.EXPECT().HistoricalProofQueryWindow().Return(queryWindow)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
backend.EXPECT().
BlockByNumberOrHash(gomock.Any(), gomock.Any()).
Return(nil, fmt.Errorf("test error"))
return backend
},
wantErrMessage: "failed to get block from hash: test error",
},
"block_number_out_of_window": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(1000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().IsArchive().Return(true)
backend.EXPECT().HistoricalProofQueryWindow().Return(queryWindow)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
return backend
},
wantErrMessage: "block number 1000 is before the oldest allowed block number 1176 (window of 1024 blocks)",
},
"block_number_out_of_window_non_archive": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(1000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().IsArchive().Return(false)
// query window is 32 as set to core.TipBufferSize
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(1033))
return backend
},
wantErrMessage: "block number 1000 is before the oldest allowed block number 1001 (window of 32 blocks)",
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)

api := &BlockChainAPI{
b: testCase.makeBackend(ctrl),
}

err := api.stateQueryBlockNumberAllowed(testCase.blockNumOrHash)
if testCase.wantErrMessage == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, testCase.wantErrMessage)
}
})
}
}
6 changes: 6 additions & 0 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,12 @@ func (b testBackend) LastAcceptedBlock() *types.Block { panic("implement me") }
func (b testBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
panic("implement me")
}
func (b testBackend) IsArchive() bool {
panic("implement me")
}
func (b testBackend) HistoricalProofQueryWindow() (queryWindow uint64) {
panic("implement me")
}

func TestEstimateGas(t *testing.T) {
t.Parallel()
Expand Down
2 changes: 2 additions & 0 deletions internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ type Backend interface {
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
BadBlocks() ([]*types.Block, []*core.BadBlockReason)
IsArchive() bool
HistoricalProofQueryWindow() uint64

// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error
Expand Down
3 changes: 3 additions & 0 deletions internal/ethapi/mocks_generate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package ethapi

//go:generate go run go.uber.org/mock/mockgen -package=$GOPACKAGE -destination=mocks_test.go . Backend
Loading
Loading