erigon-pulse/cmd/devnet/services/polygon/proofgenerator_test.go
battlmonstr 2793ef6ec1
polygon: flatten redundant packages (#9241)
* move mocks to the owner packages
* squash single file packages
* move types to more appropriate files
* remove unused mocks
2024-01-16 09:23:02 +01:00

466 lines
11 KiB
Go

package polygon
import (
"bytes"
"context"
"crypto/ecdsa"
"fmt"
"math"
"math/big"
"sync"
"testing"
"github.com/holiman/uint256"
"github.com/ledgerwatch/log/v3"
"github.com/pion/randutil"
"github.com/ledgerwatch/erigon-lib/chain"
"github.com/ledgerwatch/erigon-lib/common"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/common/hexutility"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon-lib/kv/memdb"
"github.com/ledgerwatch/erigon/accounts/abi/bind"
"github.com/ledgerwatch/erigon/cmd/devnet/blocks"
"github.com/ledgerwatch/erigon/cmd/devnet/requests"
"github.com/ledgerwatch/erigon/consensus"
"github.com/ledgerwatch/erigon/core"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/erigon/polygon/bor"
"github.com/ledgerwatch/erigon/polygon/bor/valset"
"github.com/ledgerwatch/erigon/polygon/heimdall"
"github.com/ledgerwatch/erigon/rlp"
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/turbo/jsonrpc"
"github.com/ledgerwatch/erigon/turbo/services"
"github.com/ledgerwatch/erigon/turbo/stages/mock"
"github.com/ledgerwatch/erigon/turbo/transactions"
)
type requestGenerator struct {
sync.Mutex
requests.NopRequestGenerator
sentry *mock.MockSentry
bor *bor.Bor
chain *core.ChainPack
txBlockMap map[libcommon.Hash]*types.Block
}
func newRequestGenerator(sentry *mock.MockSentry, chain *core.ChainPack) (*requestGenerator, error) {
db := memdb.New("")
if err := db.Update(context.Background(), func(tx kv.RwTx) error {
if err := rawdb.WriteHeader(tx, chain.TopBlock.Header()); err != nil {
return err
}
if err := rawdb.WriteHeadHeaderHash(tx, chain.TopBlock.Header().Hash()); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
reader := blockReader{
chain: chain,
}
return &requestGenerator{
chain: chain,
sentry: sentry,
bor: bor.NewRo(params.BorDevnetChainConfig, db, reader,
&spanner{
bor.NewChainSpanner(bor.GenesisContractValidatorSetABI(), params.BorDevnetChainConfig, false, log.Root()),
libcommon.Address{},
heimdall.Span{}},
genesisContract{}, log.Root()),
txBlockMap: map[libcommon.Hash]*types.Block{},
}, nil
}
func (rg *requestGenerator) GetRootHash(ctx context.Context, startBlock uint64, endBlock uint64) (libcommon.Hash, error) {
tx, err := rg.bor.DB.BeginRo(context.Background())
if err != nil {
return libcommon.Hash{}, err
}
defer tx.Rollback()
result, err := rg.bor.GetRootHash(ctx, tx, startBlock, endBlock)
if err != nil {
return libcommon.Hash{}, err
}
return libcommon.HexToHash(result), nil
}
func (rg *requestGenerator) GetBlockByNumber(ctx context.Context, blockNum rpc.BlockNumber, withTxs bool) (*requests.Block, error) {
if bn := int(blockNum.Uint64()); bn < len(rg.chain.Blocks) {
block := rg.chain.Blocks[bn]
transactions := make([]*jsonrpc.RPCTransaction, len(block.Transactions()))
for i, tx := range block.Transactions() {
rg.txBlockMap[tx.Hash()] = block
transactions[i] = jsonrpc.NewRPCTransaction(tx, block.Hash(), blockNum.Uint64(), uint64(i), block.BaseFee())
}
return &requests.Block{
BlockWithTxHashes: requests.BlockWithTxHashes{
Header: block.Header(),
Hash: block.Hash(),
},
Transactions: transactions,
}, nil
}
return nil, fmt.Errorf("block %d not found", blockNum.Uint64())
}
func (rg *requestGenerator) GetTransactionReceipt(ctx context.Context, hash libcommon.Hash) (*types.Receipt, error) {
rg.Lock()
defer rg.Unlock()
block, ok := rg.txBlockMap[hash]
if !ok {
return nil, fmt.Errorf("can't find block to tx: %s", hash)
}
engine := rg.bor
chainConfig := params.BorDevnetChainConfig
reader := blockReader{
chain: rg.chain,
}
tx, err := rg.sentry.DB.BeginRo(context.Background())
if err != nil {
return nil, err
}
defer tx.Rollback()
_, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, engine, block, chainConfig, reader, tx, 0, false)
if err != nil {
return nil, err
}
var usedGas uint64
var usedBlobGas uint64
gp := new(core.GasPool).AddGas(block.GasLimit()).AddBlobGas(chainConfig.GetMaxBlobGasPerBlock())
noopWriter := state.NewNoopWriter()
getHeader := func(hash common.Hash, number uint64) *types.Header {
h, e := reader.Header(ctx, tx, hash, number)
if e != nil {
log.Error("getHeader error", "number", number, "hash", hash, "err", e)
}
return h
}
header := block.Header()
for i, txn := range block.Transactions() {
ibs.SetTxContext(txn.Hash(), block.Hash(), i)
receipt, _, err := core.ApplyTransaction(chainConfig, core.GetHashFn(header, getHeader), engine, nil, gp, ibs, noopWriter, header, txn, &usedGas, &usedBlobGas, vm.Config{})
if err != nil {
return nil, err
}
if txn.Hash() == hash {
receipt.BlockHash = block.Hash()
return receipt, nil
}
}
return nil, fmt.Errorf("tx not found in block")
}
type blockReader struct {
services.FullBlockReader
chain *core.ChainPack
}
func (reader blockReader) BlockByNumber(ctx context.Context, db kv.Tx, number uint64) (*types.Block, error) {
if int(number) < len(reader.chain.Blocks) {
return reader.chain.Blocks[number], nil
}
return nil, fmt.Errorf("block not found")
}
func (reader blockReader) HeaderByNumber(ctx context.Context, tx kv.Getter, blockNum uint64) (*types.Header, error) {
if int(blockNum) < len(reader.chain.Headers) {
return reader.chain.Headers[blockNum], nil
}
return nil, fmt.Errorf("header not found")
}
func TestMerkle(t *testing.T) {
startBlock := 1600
endBlock := 3200
if depth := int(math.Ceil(math.Log2(float64(endBlock - startBlock + 1)))); depth != 11 {
t.Fatal("Unexpected depth:", depth)
}
startBlock = 0
endBlock = 100000
if depth := int(math.Ceil(math.Log2(float64(endBlock - startBlock + 1)))); depth != 17 {
t.Fatal("Unexpected depth:", depth)
}
startBlock = 0
endBlock = 500000
if depth := int(math.Ceil(math.Log2(float64(endBlock - startBlock + 1)))); depth != 19 {
t.Fatal("Unexpected depth:", depth)
}
}
func TestBlockGeneration(t *testing.T) {
_, chain, err := generateBlocks(t, 1600)
if err != nil {
t.Fatal(err)
}
reader := blockReader{
chain: chain,
}
for number := uint64(0); number < 1600; number++ {
_, err = reader.BlockByNumber(context.Background(), nil, number)
if err != nil {
t.Fatal(err)
}
header, err := reader.HeaderByNumber(context.Background(), nil, number)
if err != nil {
t.Fatal(err)
}
if header == nil {
t.Fatalf("block header not found: %d", number)
}
}
}
type genesisContract struct {
}
func (g genesisContract) CommitState(event rlp.RawValue, syscall consensus.SystemCall) error {
return nil
}
func (g genesisContract) LastStateId(syscall consensus.SystemCall) (*big.Int, error) {
return big.NewInt(0), nil
}
type spanner struct {
*bor.ChainSpanner
validatorAddress libcommon.Address
currentSpan heimdall.Span
}
func (c spanner) GetCurrentSpan(_ consensus.SystemCall) (*heimdall.Span, error) {
return &c.currentSpan, nil
}
func (c *spanner) CommitSpan(heimdallSpan heimdall.HeimdallSpan, syscall consensus.SystemCall) error {
c.currentSpan = heimdallSpan.Span
return nil
}
func (c *spanner) GetCurrentValidators(spanId uint64, signer libcommon.Address, chain consensus.ChainHeaderReader) ([]*valset.Validator, error) {
return []*valset.Validator{
{
ID: 1,
Address: c.validatorAddress,
VotingPower: 1000,
ProposerPriority: 1,
}}, nil
}
func TestBlockProof(t *testing.T) {
sentry, chain, err := generateBlocks(t, 1600)
if err != nil {
t.Fatal(err)
}
rg, err := newRequestGenerator(sentry, chain)
if err != nil {
t.Fatal(err)
}
_, err = rg.GetRootHash(context.Background(), 0, 1599)
if err != nil {
t.Fatal(err)
}
blockProofs, err := getBlockProofs(context.Background(), rg, 10, 0, 1599)
if err != nil {
t.Fatal(err)
}
if len := len(blockProofs); len != 11 {
t.Fatal("Unexpected block depth:", len)
}
if len := len(bytes.Join(blockProofs, []byte{})); len != 352 {
t.Fatal("Unexpected proof len:", len)
}
}
func TestReceiptProof(t *testing.T) {
sentry, chain, err := generateBlocks(t, 10)
if err != nil {
t.Fatal(err)
}
rg, err := newRequestGenerator(sentry, chain)
if err != nil {
t.Fatal(err)
}
var block *requests.Block
var blockNo uint64
for block == nil {
block, err = rg.GetBlockByNumber(context.Background(), rpc.AsBlockNumber(blockNo), true)
if err != nil {
t.Fatal(err)
}
if len(block.Transactions) == 0 {
block = nil
blockNo++
}
}
receipt, err := rg.GetTransactionReceipt(context.Background(), block.Transactions[len(block.Transactions)-1].Hash)
if err != nil {
t.Fatal(err)
}
receiptProof, err := getReceiptProof(context.Background(), rg, receipt, block, nil)
if err != nil {
t.Fatal(err)
}
parentNodesBytes, err := rlp.EncodeToBytes(receiptProof.parentNodes)
if err != nil {
t.Fatal(err)
}
fmt.Println(hexutility.Encode(parentNodesBytes), hexutility.Encode(append([]byte{0}, receiptProof.path...)))
}
func generateBlocks(t *testing.T, number int) (*mock.MockSentry, *core.ChainPack, error) {
data := getGenesis(3)
rand := randutil.NewMathRandomGenerator()
return blocks.GenerateBlocks(t, data.genesisSpec, number, map[int]blocks.TxGen{
0: {
Fn: getBlockTx(data.addresses[0], data.addresses[1], uint256.NewInt(uint64(rand.Intn(5000))+1)),
Key: data.keys[0],
},
1: {
Fn: getBlockTx(data.addresses[1], data.addresses[2], uint256.NewInt(uint64(rand.Intn(5000))+1)),
Key: data.keys[1],
},
2: {
Fn: getBlockTx(data.addresses[2], data.addresses[0], uint256.NewInt(uint64(rand.Intn(5000))+1)),
Key: data.keys[2],
},
}, func(_ int) int {
return rand.Intn(10)
})
}
func getBlockTx(from libcommon.Address, to libcommon.Address, amount *uint256.Int) blocks.TxFn {
return func(block *core.BlockGen, _ bind.ContractBackend) (types.Transaction, bool) {
return types.NewTransaction(block.TxNonce(from), to, amount, 21000, new(uint256.Int), nil), false
}
}
type initialData struct {
keys []*ecdsa.PrivateKey
addresses []libcommon.Address
transactOpts []*bind.TransactOpts
genesisSpec *types.Genesis
}
func getGenesis(accounts int, funds ...*big.Int) initialData {
accountFunds := big.NewInt(1000000000)
if len(funds) > 0 {
accountFunds = funds[0]
}
keys := make([]*ecdsa.PrivateKey, accounts)
for i := 0; i < accounts; i++ {
keys[i], _ = crypto.GenerateKey()
}
addresses := make([]libcommon.Address, 0, len(keys))
transactOpts := make([]*bind.TransactOpts, 0, len(keys))
allocs := types.GenesisAlloc{}
for _, key := range keys {
addr := crypto.PubkeyToAddress(key.PublicKey)
addresses = append(addresses, addr)
to, err := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1))
if err != nil {
panic(err)
}
transactOpts = append(transactOpts, to)
allocs[addr] = types.GenesisAccount{Balance: accountFunds}
}
return initialData{
keys: keys,
addresses: addresses,
transactOpts: transactOpts,
genesisSpec: &types.Genesis{
Config: &chain.Config{
ChainID: big.NewInt(1),
HomesteadBlock: new(big.Int),
TangerineWhistleBlock: new(big.Int),
SpuriousDragonBlock: big.NewInt(1),
ByzantiumBlock: big.NewInt(1),
ConstantinopleBlock: big.NewInt(1),
},
Alloc: allocs,
},
}
}