Skip to content

Commit

Permalink
historical-proofs configuration option
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Dec 26, 2024
1 parent 7a90507 commit a0ca465
Show file tree
Hide file tree
Showing 10 changed files with 902 additions and 0 deletions.
5 changes: 5 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type EthAPIBackend struct {
allowUnprotectedTxs bool
allowUnprotectedTxHashes map[common.Hash]struct{} // Invariant: read-only after creation.
allowUnfinalizedQueries bool
historicalProofs bool
eth *Ethereum
gpo *gasprice.Oracle
}
Expand All @@ -67,6 +68,10 @@ func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
return b.eth.blockchain.Config()
}

func (b *EthAPIBackend) HistoricalConfig() (historicalProofs bool) {
return b.historicalProofs
}

func (b *EthAPIBackend) IsAllowUnfinalizedQueries() bool {
return b.allowUnfinalizedQueries
}
Expand Down
1 change: 1 addition & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ func New(
allowUnprotectedTxs: config.AllowUnprotectedTxs,
allowUnprotectedTxHashes: allowUnprotectedTxHashes,
allowUnfinalizedQueries: config.AllowUnfinalizedQueries,
historicalProofs: config.HistoricalProofs,
eth: eth,
}
if config.AllowUnprotectedTxs {
Expand Down
1 change: 1 addition & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type Config struct {
SnapshotWait bool // Whether to wait for the initial snapshot generation
SnapshotVerify bool // Whether to verify generated snapshots
SkipSnapshotRebuild bool // Whether to skip rebuilding the snapshot in favor of returning an error (only set to true for tests)
HistoricalProofs bool // HistoricalProofs, if set to true, allows to query historical blocks for proofs.

// Database options
SkipBcVersionCheck bool `toml:"-"`
Expand Down
44 changes: 44 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,13 @@ 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, an error is returned.
func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
err := s.isAllowedToBeQueried(blockNrOrHash)
if err != nil {
return nil, fmt.Errorf("checking if block is allowed to be queried: %w", err)
}

var (
keys = make([]common.Hash, len(storageKeys))
keyLengths = make([]int, len(storageKeys))
Expand Down Expand Up @@ -764,6 +770,44 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
}, statedb.Error()
}

// isAllowedToBeQueried returns a nil error only if either:
// - the node is configured to accept historical proofs queries; or
// - the block number given is within the window of recent accepted
// blocks and not yet part of the historical blocks.
//
// Otherwise, a non-nil error is returned.
func (s *BlockChainAPI) isAllowedToBeQueried(blockNumOrHash rpc.BlockNumberOrHash) (err error) {
historicalProofs := s.b.HistoricalConfig()
if historicalProofs {
return nil
}

lastBlock := s.b.LastAcceptedBlock()
lastNumber := lastBlock.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("getting block from hash: %s", err)
}
number = block.NumberU64()
}

const latestBlocksWindow = 1024 // TODO: find a decent default for validators
var oldestContemporaryNumber uint64
if lastNumber > latestBlocksWindow {
oldestContemporaryNumber = lastNumber - latestBlocksWindow
}
if number >= oldestContemporaryNumber {
return nil
}
return fmt.Errorf("block number %d is before the oldest non-historical block number %d (window of %d blocks)",
number, oldestContemporaryNumber, latestBlocksWindow)
}

// decodeHash parses a hex-encoded 32-byte hash. The input may optionally
// be prefixed by 0x and can have a byte length up to 32.
func decodeHash(s string) (h common.Hash, inputLength int, err error) {
Expand Down
102 changes: 102 additions & 0 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/exp/slices"
)

Expand Down Expand Up @@ -625,6 +627,9 @@ 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) HistoricalConfig() (historicalBlocks bool) {
panic("implement me")
}

func TestEstimateGas(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -2062,3 +2067,100 @@ func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc s
}
require.JSONEqf(t, string(want), string(data), "test %d: json not match, want: %s, have: %s", testid, string(want), string(data))
}

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

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
}{
"historical_proofs_accepted": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(0)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().HistoricalConfig().Return(true)
return backend
},
},
"block_number_recent_below_window": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(1000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().HistoricalConfig().Return(false)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(1020))
return backend
},
},
"block_number_recent": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(2000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().HistoricalConfig().Return(false)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
return backend
},
},
"block_number_recent_by_hash": {
blockNumOrHash: rpc.BlockNumberOrHashWithHash(common.Hash{99}, false),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().HistoricalConfig().Return(false)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
backend.EXPECT().
BlockByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithHash(common.Hash{99}, false)).
Return(makeBlockWithNumber(2000), nil)
return backend
},
},
"block_number_recent_by_hash_error": {
blockNumOrHash: rpc.BlockNumberOrHashWithHash(common.Hash{99}, false),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().HistoricalConfig().Return(false)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
backend.EXPECT().
BlockByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithHash(common.Hash{99}, false)).
Return(nil, fmt.Errorf("test error"))
return backend
},
wantErrMessage: "getting block from hash: test error",
},
"block_number_historical": {
blockNumOrHash: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(1000)),
makeBackend: func(ctrl *gomock.Controller) *MockBackend {
backend := NewMockBackend(ctrl)
backend.EXPECT().HistoricalConfig().Return(false)
backend.EXPECT().LastAcceptedBlock().Return(makeBlockWithNumber(2200))
return backend
},
wantErrMessage: "block number 1000 is before the oldest non-historical block number 1176 (window of 1024 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.isAllowedToBeQueried(testCase.blockNumOrHash)
if testCase.wantErrMessage == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, testCase.wantErrMessage)
}
})
}
}
1 change: 1 addition & 0 deletions internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type Backend interface {
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
BadBlocks() ([]*types.Block, []*core.BadBlockReason)
HistoricalConfig() (historicalBlocks bool)

// 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 mockgen -package=$GOPACKAGE -destination=mocks_test.go . Backend
Loading

0 comments on commit a0ca465

Please sign in to comment.