Skip to content

Commit

Permalink
Implement output-batch-affiliation command
Browse files Browse the repository at this point in the history
  • Loading branch information
Stefan-Ethernal committed Aug 28, 2024
1 parent 5f620e4 commit 88dd4fa
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 5 deletions.
21 changes: 21 additions & 0 deletions zk/debug_tools/mdbx-data-browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ In case `file-output` flag is provided, results are printed to a JSON file (othe

**Note:** In case, `output-blocks` is ran with `verbose` flag provided, it is necessary to provide the proper chain id to the `params/chainspecs/mainnet.json`. This is the case, because CDK Erigon (for now) uses hardcoded data to recover transaction senders, and chain id information is read from the mentioned file.

#### `output-batch-affiliation`
It is used to output the batch numbers alongside with the blocks that affiliate to the certain batch.

- **Name**: `output-batch-affiliation`
- **Usage**: Outputs batch affiliation of certain blocks.
- **Action**: `dumpBatchAffiliation`
- **Flags**:
- `data-dir`: Specifies the data directory to use.
- `bn`: Block numbers.
- **Name**: `bn`
- **Usage**: Block numbers.
- **Destination**: `batchOrBlockNumbers`
- `verbose`: See [verbose](#verbose) flag.
- `file-output`: See [file-output](#file-output) flag.

### Example Usage

**Pre-requisite:** Navigate to the `zk/debug_tools/mdbx-data-browser` folder and run `go build -o mdbx-data-browser`
Expand All @@ -75,3 +90,9 @@ In case `file-output` flag is provided, results are printed to a JSON file (othe
```sh
./mdbx-data-browser output-blocks --datadir chaindata/ --bn 100,101,102 [--verbose] [--file-output]
```

#### `output-batch-affiliation` Command

```sh
./mdbx-data-browser output-batch-affiliation --datadir chaindata/ --bn 100,101,102 [--verbose] [--file-output]
```
47 changes: 46 additions & 1 deletion zk/debug_tools/mdbx-data-browser/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ var (
},
}

getBatchAffiliationCmd = &cli.Command{
Action: dumpBatchAffiliation,
Name: "output-batch-affiliation",
Usage: "Outputs batch affiliation for provided block numbers",
Flags: []cli.Flag{
&utils.DataDirFlag,
&cli.Uint64SliceFlag{
Name: "bn",
Usage: "Block numbers",
Destination: batchOrBlockNumbers,
},
fileOutputFlag,
},
}

// parameters
chainDataDir string
batchOrBlockNumbers *cli.Uint64Slice = cli.NewUint64Slice()
Expand Down Expand Up @@ -148,6 +163,36 @@ func dumpBlocksByNumbers(cliCtx *cli.Context) error {
return nil
}

// dumpBatchAffiliation retrieves batch numbers by given block numbers and dumps them either on standard output or to a file
func dumpBatchAffiliation(cliCtx *cli.Context) error {
if !cliCtx.IsSet(utils.DataDirFlag.Name) {
return errors.New("chain data directory is not provided")
}

chainDataDir = cliCtx.String(utils.DataDirFlag.Name)

tx, cleanup, err := createDbTx(chainDataDir, cliCtx.Context)
if err != nil {
return fmt.Errorf("failed to create read-only db transaction: %w", err)
}
defer cleanup()

r := NewDbDataRetriever(tx)
batchInfo, err := r.GetBatchAffiliation(batchOrBlockNumbers.Value())
if err != nil {
return err
}
jsonBatchAffiliation, err := json.MarshalIndent(batchInfo, "", " ")
if err != nil {
return fmt.Errorf("failed to serialize batch affiliation info into the JSON format: %w", err)
}

if err := outputResults(string(jsonBatchAffiliation)); err != nil {
return fmt.Errorf("failed to output results: %w", err)
}
return nil
}

// createDbTx creates a read-only database transaction, that allows querying it.
func createDbTx(chainDataDir string, ctx context.Context) (kv.Tx, func(), error) {
db := mdbx.MustOpen(chainDataDir)
Expand Down Expand Up @@ -183,7 +228,7 @@ func outputResults(results string) error {
return err
}

fmt.Printf("results are written to the '%s'", path)
fmt.Printf("results are written to the '%s'\n", path)
return nil
}

Expand Down
59 changes: 59 additions & 0 deletions zk/debug_tools/mdbx-data-browser/dbdata_retriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"errors"
"fmt"
"sort"

"github.com/gateway-fm/cdk-erigon-lib/kv"

Expand All @@ -11,6 +12,7 @@ import (
coreTypes "github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/zk/hermez_db"
rpcTypes "github.com/ledgerwatch/erigon/zk/rpcdaemon"
zktx "github.com/ledgerwatch/erigon/zk/tx"
"github.com/ledgerwatch/erigon/zk/utils"
)

Expand Down Expand Up @@ -216,6 +218,25 @@ func (d *DbDataRetriever) populateBlocksAndTransactions(batch *rpcTypes.Batch, b
return err
}

l2TxHash, err := zktx.ComputeL2TxHash(
tx.GetChainID().ToBig(),
tx.GetValue(),
tx.GetPrice(),
tx.GetNonce(),
tx.GetGas(),
tx.GetTo(),
&rpcTx.From,
tx.GetData(),
)
if err != nil {
return err
}

if rpcTx.Receipt != nil {
rpcTx.Receipt.TransactionL2Hash = l2TxHash
}
rpcTx.L2Hash = l2TxHash

batch.Transactions = append(batch.Transactions, rpcTx)
}
}
Expand Down Expand Up @@ -270,8 +291,46 @@ func (d *DbDataRetriever) GetBlockByNumber(blockNum uint64, includeTxs, includeR
return d.convertToRPCBlock(block, verboseOutput, verboseOutput)
}

// GetBatchAffiliation retrieves the batch affiliation for the provided block numbers
func (d *DbDataRetriever) GetBatchAffiliation(blocks []uint64) ([]*BatchAffiliationInfo, error) {
batchInfoMap := make(map[uint64]*BatchAffiliationInfo)
for _, blockNum := range blocks {
batchNum, err := d.dbReader.GetBatchNoByL2Block(blockNum)
if err != nil {
return nil, err
}

if blockNum > 0 && batchNum == 0 {
return nil, fmt.Errorf("batch is not found for block num %d", blockNum)
}

batchInfo, exists := batchInfoMap[batchNum]
if !exists {
batchInfo = &BatchAffiliationInfo{Number: batchNum}
batchInfoMap[batchNum] = batchInfo
}
batchInfo.Blocks = append(batchInfo.Blocks, blockNum)
}

res := make([]*BatchAffiliationInfo, 0, len(batchInfoMap))
for _, bi := range batchInfoMap {
res = append(res, bi)
}

sort.Slice(res, func(i, j int) bool {
return res[i].Number < res[j].Number
})

return res, nil
}

// convertToRPCBlock converts the coreTypes.Block into rpcTypes.Block
func (d *DbDataRetriever) convertToRPCBlock(block *coreTypes.Block, includeTxs, includeReceipts bool) (*rpcTypes.Block, error) {
receipts := rawdb.ReadReceipts(d.tx, block, block.Body().SendersFromTxs())
return rpcTypes.NewBlock(block, receipts.ToSlice(), includeTxs, includeReceipts)
}

type BatchAffiliationInfo struct {
Number uint64 `json:"batch"`
Blocks []uint64 `json:"blocks"`
}
85 changes: 81 additions & 4 deletions zk/debug_tools/mdbx-data-browser/dbdata_retriever_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ func TestDbDataRetrieverGetBatchByNumber(t *testing.T) {
func TestDbDataRetrieverGetBlockByNumber(t *testing.T) {
t.Run("querying an existing block", func(t *testing.T) {
// arrange
_, dbTx := memdb.NewTestTx(t)
_, tx := memdb.NewTestTx(t)
tx1 := types.NewTransaction(1, libcommon.HexToAddress("0x1050"), u256.Num1, 1, u256.Num1, nil)
tx2 := types.NewTransaction(2, libcommon.HexToAddress("0x100"), u256.Num27, 2, u256.Num2, nil)

block := createBlock(t, 5, types.Transactions{tx1, tx2})

require.NoError(t, rawdb.WriteCanonicalHash(dbTx, block.Hash(), block.NumberU64()))
require.NoError(t, rawdb.WriteBlock(dbTx, block))
require.NoError(t, rawdb.WriteCanonicalHash(tx, block.Hash(), block.NumberU64()))
require.NoError(t, rawdb.WriteBlock(tx, block))

// act and assert
dbReader := NewDbDataRetriever(dbTx)
dbReader := NewDbDataRetriever(tx)
result, err := dbReader.GetBlockByNumber(block.NumberU64(), true, true)
require.NoError(t, err)
require.Equal(t, block.Hash(), result.Hash)
Expand All @@ -91,6 +91,83 @@ func TestDbDataRetrieverGetBlockByNumber(t *testing.T) {
})
}

func TestDbDataRetrieverGetBatchAffiliation(t *testing.T) {
testCases := []struct {
name string
blocksInBatch int
batchesCount int
blockNums []uint64
expectedErrMsg string
expectedResult []*BatchAffiliationInfo
}{
{
name: "Basic case with three blocks and two requested",
blocksInBatch: 3,
batchesCount: 2,
blockNums: []uint64{1, 3},
expectedResult: []*BatchAffiliationInfo{
{Number: 1, Blocks: []uint64{1, 3}},
},
},
{
name: "All blocks in batch requested",
blocksInBatch: 3,
batchesCount: 2,
blockNums: []uint64{4, 5, 6},
expectedResult: []*BatchAffiliationInfo{
{Number: 2, Blocks: []uint64{4, 5, 6}},
},
},
{
name: "Request multiple batches",
blocksInBatch: 2,
batchesCount: 3,
blockNums: []uint64{1, 2, 6},
expectedResult: []*BatchAffiliationInfo{
{Number: 1, Blocks: []uint64{1, 2}},
{Number: 3, Blocks: []uint64{6}},
},
},
{
name: "Request non-existent block",
blocksInBatch: 2,
batchesCount: 2,
blockNums: []uint64{5},
expectedErrMsg: "batch is not found for block num 5",
expectedResult: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, dbTx := memdb.NewTestTx(t)
require.NoError(t, hermez_db.CreateHermezBuckets(dbTx))
db := hermez_db.NewHermezDb(dbTx)

// Write the blocks according to the test case
blockNum := uint64(1)
for batchNum := uint64(1); batchNum <= uint64(tc.batchesCount); batchNum++ {
for i := 0; i < tc.blocksInBatch; i++ {
require.NoError(t, db.WriteBlockBatch(blockNum, batchNum))
blockNum++
}
}

dbReader := NewDbDataRetriever(dbTx)
batchAffiliation, err := dbReader.GetBatchAffiliation(tc.blockNums)

// Check if an error was expected
if tc.expectedErrMsg != "" {
require.ErrorContains(t, err, tc.expectedErrMsg)
} else {
require.NoError(t, err)
}

require.Equal(t, tc.expectedResult, batchAffiliation)
})
}
}

// createBlock is a helper function, that allows creating block
func createBlock(t *testing.T, number uint64, txs types.Transactions) *types.Block {
t.Helper()
Expand Down
1 change: 1 addition & 0 deletions zk/debug_tools/mdbx-data-browser/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func main() {
app.Commands = []*cli.Command{
getBatchByNumberCmd,
getBlockByNumberCmd,
getBatchAffiliationCmd,
}

logging.SetupLogger("mdbx data browser")
Expand Down

0 comments on commit 88dd4fa

Please sign in to comment.