mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-28 15:46:43 +00:00
internal/era: random access to header and receipts
This commit is contained in:
parent
9f83e9e673
commit
e4e4c5538b
2 changed files with 219 additions and 0 deletions
|
|
@ -126,6 +126,29 @@ func (e *Era) Close() error {
|
|||
return e.f.Close()
|
||||
}
|
||||
|
||||
// GetHeaderByNumber returns the header for the given block number.
|
||||
func (e *Era) GetHeaderByNumber(num uint64) (*types.Header, error) {
|
||||
if e.m.start > num || e.m.start+e.m.count <= num {
|
||||
return nil, errors.New("out-of-bounds")
|
||||
}
|
||||
off, err := e.readOffset(num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read and decompress header
|
||||
r, _, err := newSnappyReader(e.s, TypeCompressedHeader, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var header types.Header
|
||||
if err := rlp.Decode(r, &header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &header, nil
|
||||
}
|
||||
|
||||
// GetBlockByNumber returns the block for the given block number.
|
||||
func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
|
||||
if e.m.start > num || e.m.start+e.m.count <= num {
|
||||
return nil, errors.New("out-of-bounds")
|
||||
|
|
@ -154,6 +177,42 @@ func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
|
|||
return types.NewBlockWithHeader(&header).WithBody(body), nil
|
||||
}
|
||||
|
||||
// GetReceipts returns the receipts for the given block number.
|
||||
func (e *Era) GetReceipts(num uint64) (types.Receipts, error) {
|
||||
if e.m.start > num || e.m.start+e.m.count <= num {
|
||||
return nil, errors.New("out-of-bounds")
|
||||
}
|
||||
off, err := e.readOffset(num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Skip header entry
|
||||
headerLength, err := e.s.LengthAt(off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off += headerLength
|
||||
|
||||
// Skip body entry
|
||||
bodyLength, err := e.s.LengthAt(off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off += bodyLength
|
||||
|
||||
// Read and decompress receipts
|
||||
r, _, err := newSnappyReader(e.s, TypeCompressedReceipts, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var receipts types.Receipts
|
||||
if err := rlp.Decode(r, &receipts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return receipts, nil
|
||||
}
|
||||
|
||||
// Accumulator reads the accumulator entry in the Era1 file.
|
||||
func (e *Era) Accumulator() (common.Hash, error) {
|
||||
entry, err := e.s.Find(TypeAccumulator)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,17 @@ import (
|
|||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testchain struct {
|
||||
|
|
@ -144,3 +152,155 @@ func TestEraFilename(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func genTestChain(t *testing.T) (*core.Genesis, []*types.Block, []types.Receipts) {
|
||||
privateKey, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
var (
|
||||
address = crypto.PubkeyToAddress(privateKey.PublicKey)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: types.GenesisAlloc{
|
||||
address: {
|
||||
Balance: big.NewInt(10000000000000000), // 10 ETH
|
||||
},
|
||||
},
|
||||
GasLimit: 1000000,
|
||||
Difficulty: big.NewInt(1),
|
||||
}
|
||||
)
|
||||
_, chain, receipts := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 3, func(i int, gen *core.BlockGen) {
|
||||
// Add a transfer transaction
|
||||
to := common.HexToAddress("0x5678")
|
||||
tx := types.NewTx(&types.LegacyTx{
|
||||
Nonce: uint64(i * 2),
|
||||
To: &to,
|
||||
Value: big.NewInt(100000000000000), // 0.1 ETH
|
||||
Gas: 21000,
|
||||
GasPrice: big.NewInt(1000000000), // 1 Gwei
|
||||
})
|
||||
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(genesis.Config.ChainID), privateKey)
|
||||
require.NoError(t, err)
|
||||
gen.AddTx(signedTx)
|
||||
|
||||
// Add a contract creation transaction
|
||||
tx = types.NewTx(&types.LegacyTx{
|
||||
Nonce: uint64(i*2 + 1),
|
||||
Value: big.NewInt(0),
|
||||
Gas: 100000,
|
||||
GasPrice: big.NewInt(1000000000), // 1 Gwei
|
||||
// Simple contract that returns empty data
|
||||
Data: []byte{
|
||||
0x60, 0x00, // PUSH1 0
|
||||
0x60, 0x00, // PUSH1 0
|
||||
0x52, // MSTORE
|
||||
0x60, 0x20, // PUSH1 32
|
||||
0x60, 0x00, // PUSH1 0
|
||||
0xf3, // RETURN
|
||||
},
|
||||
})
|
||||
signedTx, err = types.SignTx(tx, types.NewEIP155Signer(genesis.Config.ChainID), privateKey)
|
||||
require.NoError(t, err)
|
||||
gen.AddTx(signedTx)
|
||||
})
|
||||
return genesis, chain, receipts
|
||||
}
|
||||
|
||||
// exportChain creates a temporary era file with the given chain.
|
||||
func exportChain(t *testing.T, genesis *core.Genesis, chain []*types.Block, receipts []types.Receipts) (string, string) {
|
||||
tmpDir, err := os.MkdirTemp("", "era-test-*")
|
||||
require.NoError(t, err)
|
||||
const fileName = "test.era1"
|
||||
tmpFile, err := os.Create(filepath.Join(tmpDir, fileName))
|
||||
require.NoError(t, err)
|
||||
|
||||
builder := NewBuilder(tmpFile)
|
||||
// Add blocks to era
|
||||
for i, block := range chain {
|
||||
td := new(big.Int).Add(genesis.Difficulty, big.NewInt(int64(i+1)))
|
||||
headerData, err := rlp.EncodeToBytes(block.Header())
|
||||
require.NoError(t, err)
|
||||
bodyData, err := rlp.EncodeToBytes(block.Body())
|
||||
require.NoError(t, err)
|
||||
receiptsData, err := rlp.EncodeToBytes(receipts[i])
|
||||
require.NoError(t, err)
|
||||
|
||||
err = builder.AddRLP(headerData, bodyData, receiptsData, block.NumberU64(), block.Hash(), td, block.Difficulty())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err = builder.Finalize()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tmpFile.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
return tmpDir, fileName
|
||||
}
|
||||
|
||||
func TestEraFunctions(t *testing.T) {
|
||||
genesis, blocks, receipts := genTestChain(t)
|
||||
testdir, filename := exportChain(t, genesis, blocks, receipts)
|
||||
defer os.RemoveAll(testdir)
|
||||
// Open the era file
|
||||
era, err := Open(filepath.Join(testdir, filename))
|
||||
require.NoError(t, err)
|
||||
defer era.Close()
|
||||
|
||||
t.Run("GetHeaderByNumber", func(t *testing.T) {
|
||||
header, err := era.GetHeaderByNumber(era.Start())
|
||||
require.NoError(t, err)
|
||||
haveJson, err := rlp.EncodeToBytes(header)
|
||||
require.NoError(t, err)
|
||||
wantJson, err := rlp.EncodeToBytes(blocks[0].Header())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantJson, haveJson)
|
||||
|
||||
header, err = era.GetHeaderByNumber(era.Start() + era.Count() - 1)
|
||||
require.NoError(t, err)
|
||||
haveJson, err = rlp.EncodeToBytes(header)
|
||||
require.NoError(t, err)
|
||||
wantJson, err = rlp.EncodeToBytes(blocks[2].Header())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantJson, haveJson)
|
||||
})
|
||||
|
||||
t.Run("GetBlockByNumber", func(t *testing.T) {
|
||||
block, err := era.GetBlockByNumber(era.Start())
|
||||
require.NoError(t, err)
|
||||
haveJson, err := rlp.EncodeToBytes(block)
|
||||
require.NoError(t, err)
|
||||
wantJson, err := rlp.EncodeToBytes(blocks[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantJson, haveJson)
|
||||
|
||||
block, err = era.GetBlockByNumber(era.Start() + 1)
|
||||
require.NoError(t, err)
|
||||
haveJson, err = rlp.EncodeToBytes(block)
|
||||
require.NoError(t, err)
|
||||
wantJson, err = rlp.EncodeToBytes(blocks[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantJson, haveJson)
|
||||
})
|
||||
|
||||
t.Run("GetReceipts", func(t *testing.T) {
|
||||
rcpts, err := era.GetReceipts(era.Start())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(rcpts)) // Should have 2 receipts
|
||||
|
||||
require.Equal(t, receipts[0][0].CumulativeGasUsed, rcpts[0].CumulativeGasUsed)
|
||||
require.Equal(t, receipts[0][1].CumulativeGasUsed, rcpts[1].CumulativeGasUsed)
|
||||
require.Equal(t, receipts[0][0].Status, rcpts[0].Status)
|
||||
require.Equal(t, receipts[0][1].Status, rcpts[0].Status)
|
||||
})
|
||||
|
||||
t.Run("OutOfBounds", func(t *testing.T) {
|
||||
_, err := era.GetHeaderByNumber(era.Start() - 1)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "out-of-bounds", err.Error())
|
||||
|
||||
_, err = era.GetHeaderByNumber(era.Start() + era.Count())
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "out-of-bounds", err.Error())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue