Compare commits

...

2 Commits

Author SHA1 Message Date
Morty
40d6a228cc feat: add revert batch logic 2025-12-01 18:13:03 +08:00
Morty
75b6737820 feat: add test l1 reorg 2025-11-25 00:06:04 +08:00
12 changed files with 322 additions and 13 deletions

View File

@@ -17,6 +17,7 @@ var (
&MetricsPort,
&ServicePortFlag,
&Genesis,
&RevertFlag,
}
// RollupRelayerFlags contains flags only used in rollup-relayer
RollupRelayerFlags = []cli.Flag{
@@ -26,6 +27,10 @@ var (
ProposerToolFlags = []cli.Flag{
&StartL2BlockFlag,
}
RevertFlag = cli.BoolFlag{
Name: "revert",
Usage: "To revert the batch",
}
// ConfigFileFlag load json type config file.
ConfigFileFlag = cli.StringFlag{
Name: "config",

View File

@@ -1,5 +1,5 @@
{
"dsn": "postgres://localhost/scroll?sslmode=disable",
"dsn": "postgres://postgres:postgres@localhost:5432/scroll?sslmode=disable",
"driver_name": "postgres",
"maxOpenNum": 200,
"maxIdleNum": 20

0
database/genesis.json Normal file
View File

View File

@@ -0,0 +1 @@

File diff suppressed because one or more lines are too long

View File

@@ -144,6 +144,14 @@ func action(ctx *cli.Context) error {
return nil
}
if ctx.Bool(utils.RevertFlag.Name) {
err = l2relayer.RevertBatch(7)
if err != nil {
log.Crit("failed to revert batch", "error", err)
}
os.Exit(0)
}
// Watcher loop to fetch missing blocks
go utils.LoopWithContext(subCtx, 2*time.Second, func(ctx context.Context) {
number, loopErr := rutils.GetLatestConfirmedBlockNumber(ctx, l2ethClient, cfg.L2Config.Confirmations)

86
rollup/config.json Normal file
View File

@@ -0,0 +1,86 @@
{
"l2_config": {
"confirmations": "0x1",
"endpoint": "http://localhost:8545",
"l2_message_queue_address": "0x5300000000000000000000000000000000000000",
"relayer_config": {
"validium_mode": false,
"rollup_contract_address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707",
"sender_config": {
"endpoint": "http://localhost:8544",
"escalate_blocks": 1,
"confirmations": "0x0",
"escalate_multiple_num": 2,
"escalate_multiple_den": 1,
"max_gas_price": 1000000000000,
"max_blob_gas_price": 10000000000000,
"tx_type": "DynamicFeeTx",
"check_pending_time": 1,
"min_gas_tip": 100000000,
"max_pending_blob_txs": 3,
"fusaka_timestamp": 9999999999999
},
"batch_submission": {
"min_batches": 1,
"max_batches": 1,
"timeout": 8400,
"backlog_max": 0
},
"gas_oracle_config": {
"min_gas_price": 0,
"gas_price_diff": 50000
},
"chain_monitor": {
"enabled": false,
"timeout": 3,
"try_times": 5,
"base_url": "http://localhost:8750"
},
"enable_test_env_bypass_features": true,
"test_env_bypass_only_until_fork_boundary": false,
"finalize_batch_without_proof_timeout_sec": 200,
"finalize_bundle_without_proof_timeout_sec": 1000000,
"gas_oracle_sender_signer_config": {
"signer_type": "PrivateKey",
"private_key_signer_config": {
"private_key": "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
}
},
"commit_sender_signer_config": {
"signer_type": "PrivateKey",
"private_key_signer_config": {
"private_key": "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
}
},
"finalize_sender_signer_config": {
"signer_type": "PrivateKey",
"private_key_signer_config": {
"private_key": "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
}
}
},
"chunk_proposer_config": {
"propose_interval_milliseconds": 100,
"max_l2_gas_per_chunk": 20000000,
"chunk_timeout_sec": 30,
"max_uncompressed_batch_bytes_size": 4194304
},
"batch_proposer_config": {
"propose_interval_milliseconds": 1000,
"batch_timeout_sec": 300,
"max_chunks_per_batch": 1,
"max_uncompressed_batch_bytes_size": 4194304
},
"bundle_proposer_config": {
"max_batch_num_per_bundle": 1,
"bundle_timeout_sec": 300
}
},
"db_config": {
"driver_name": "postgres",
"dsn": "postgres://postgres:postgres@localhost:5432/scroll?sslmode=disable",
"maxOpenNum": 200,
"maxIdleNum": 20
}
}

136
rollup/genesis.json Normal file
View File

@@ -0,0 +1,136 @@
{
"config": {
"chainId": 534351,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"archimedesBlock": 0,
"shanghaiBlock": 0,
"bernoulliBlock": 0,
"curieBlock": 0,
"darwinTime": 0,
"darwinV2Time": 0,
"euclidTime": 0,
"euclidV2Time": 0,
"feynmanTime": 0,
"clique": {
"period": 3,
"epoch": 30000
},
"systemContract": {
"period": 1,
"blocks_per_second": 2,
"system_contract_address": "0xC706Ba9fa4fedF4507CB7A898b4766c1bbf9be57",
"system_contract_slot": "0x0000000000000000000000000000000000000000000000000000000000000067"
},
"scroll": {
"useZktrie": true,
"maxTxPayloadBytesPerBlock": 122880,
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
"l1Config": {
"l1ChainId": "11155111",
"l1MessageQueueAddress": "0xF0B2293F5D834eAe920c6974D50957A1732de763",
"l1MessageQueueV2Address": "0xA0673eC0A48aa924f067F1274EcD281A10c5f19F",
"l1MessageQueueV2DeploymentBlock": 7773746,
"scrollChainAddress": "0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0",
"l2SystemConfigAddress": "0xF444cF06A3E3724e20B35c2989d3942ea8b59124",
"numL1MessagesPerBlock": "10"
},
"genesisStateRoot": "0x20695989e9038823e35f0e88fbc44659ffdbfa1fe89fbeb2689b43f15fa64cb5",
"missingHeaderFieldsSHA256": "0xa02354c12ca0f918bf4768255af9ed13c137db7e56252348f304b17bb4088924"
}
},
"nonce": "0x0",
"timestamp": "0x6490fdd2",
"extraData": "0x",
"gasLimit": "0x1312D00",
"baseFeePerGas": "0x0",
"difficulty": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x90F79bf6EB2c4f870365E785982E1f101E93b906": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x976EA74026E726554dB657fA54763abd0C3a0aa9": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x14dC79964da2C08b23698B3D3cc7Ca32193d9955": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0xa0Ee7A142d267C1f36714E4a8F75612F20a79720": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0xBcd4042DE499D14e55001CcbB24a551F3b954096": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x71bE63f3384f5fb98995898A86B02Fb2426c5788": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0xFABB0ac9d68B0B445fB7357272Ff202C5651694a": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0xcd3B766CCDd6AE721141F452C550Ca635964ce71": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x2546BcD3c84621e976D8185a91A922aE77ECEc30": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0xbDA5747bFD65F08deb54cb465eB87D40e51B197E": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0xdD2FD4581271e230360230F9337D5c0430Bf44C0": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199": {
"balance": "0xD3C21BCECCEDA1000000"
},
"0x5300000000000000000000000000000000000002": {
"balance": "0xd3c21bcecceda1000000",
"storage": {
"0x01": "0x000000000000000000000000000000000000000000000000000000003758e6b0",
"0x02": "0x0000000000000000000000000000000000000000000000000000000000000038",
"0x03": "0x000000000000000000000000000000000000000000000000000000003e95ba80",
"0x04": "0x0000000000000000000000005300000000000000000000000000000000000003",
"0x05": "0x000000000000000000000000000000000000000000000000000000008390c2c1",
"0x06": "0x00000000000000000000000000000000000000000000000000000069cf265bfe",
"0x07": "0x00000000000000000000000000000000000000000000000000000000168b9aa3"
}
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

View File

@@ -123,18 +123,19 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.
switch serviceType {
case ServiceTypeL2RollupRelayer:
commitSenderAddr, err := addrFromSignerConfig(cfg.CommitSenderSignerConfig)
if err != nil {
return nil, fmt.Errorf("failed to parse addr from commit sender config, err: %v", err)
}
finalizeSenderAddr, err := addrFromSignerConfig(cfg.FinalizeSenderSignerConfig)
if err != nil {
return nil, fmt.Errorf("failed to parse addr from finalize sender config, err: %v", err)
}
if commitSenderAddr == finalizeSenderAddr {
return nil, fmt.Errorf("commit and finalize sender addresses must be different. Got: Commit=%s, Finalize=%s", commitSenderAddr.Hex(), finalizeSenderAddr.Hex())
}
// commitSenderAddr, err := addrFromSignerConfig(cfg.CommitSenderSignerConfig)
// if err != nil {
// return nil, fmt.Errorf("failed to parse addr from commit sender config, err: %v", err)
// }
// finalizeSenderAddr, err := addrFromSignerConfig(cfg.FinalizeSenderSignerConfig)
// if err != nil {
// return nil, fmt.Errorf("failed to parse addr from finalize sender config, err: %v", err)
// }
// if commitSenderAddr == finalizeSenderAddr {
// return nil, fmt.Errorf("commit and finalize sender addresses must be different. Got: Commit=%s, Finalize=%s", commitSenderAddr.Hex(), finalizeSenderAddr.Hex())
// }
var err error
commitSender, err = sender.NewSender(ctx, cfg.SenderConfig, cfg.CommitSenderSignerConfig, "l2_relayer", "commit_sender", types.SenderTypeCommitBatch, db, reg)
if err != nil {
return nil, fmt.Errorf("new commit sender failed, err: %w", err)
@@ -339,6 +340,28 @@ func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte,
}
}
func (r *Layer2Relayer) RevertBatch(batchIndex uint64) error {
batch, err := r.batchOrm.GetBatchByIndex(r.ctx, batchIndex)
if err != nil {
return fmt.Errorf("failed to get batch header by index: %v", err)
}
calldata, packErr := r.l1RollupABI.Pack("revertBatch", batch.BatchHeader)
if packErr != nil {
return fmt.Errorf("failed to pack rollup revertBatch with batch header: %v. error: %v", common.Bytes2Hex(batch.BatchHeader), packErr)
}
// submit genesis batch to L1 rollup contract
log.Info("--------------Morty------------", "calldata", common.Bytes2Hex(calldata))
txHash, _, err := r.commitSender.SendTransaction("revertBatch_"+batch.Hash, &r.cfg.RollupContractAddress, calldata, nil)
if err != nil {
return fmt.Errorf("failed to send import genesis batch tx to L1, error: %v", err)
}
log.Info("RevertBatch transaction sent", "contract", r.cfg.RollupContractAddress, "txHash", txHash, "batchIndex", batch.Index, "validium", r.cfg.ValidiumMode)
return nil
}
// ProcessPendingBatches processes the pending batches by sending commitBatch transactions to layer 1.
// Pending batches are submitted if one of the following conditions is met:
// - the first batch is too old -> forceSubmit

View File

@@ -28,6 +28,7 @@ import (
"scroll-tech/rollup/internal/config"
"scroll-tech/rollup/internal/orm"
"scroll-tech/rollup/internal/utils"
cutils "scroll-tech/common/utils"
)
const (
@@ -320,6 +321,13 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
version = gethTypes.BlobSidecarVersion1
}
versionedBlobHash, err := cutils.CalculateVersionedBlobHash(*blobs[0])
if err != nil {
log.Error("failed to calculate versioned blob hash", "err", err)
return common.Hash{}, 0, fmt.Errorf("failed to calculate versioned blob hash, err: %w", err)
}
log.Info("--------------Morty------------", "versionedBlobHash", common.Bytes2Hex(versionedBlobHash[:]))
sidecar, err = makeSidecar(version, blobs)
if err != nil {
log.Error("failed to make sidecar for blob transaction", "error", err)
@@ -348,6 +356,13 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
return common.Hash{}, 0, fmt.Errorf("failed to insert transaction, err: %w", err)
}
rawTx, err := signedTx.MarshalBinary()
if err != nil {
log.Error("failed to marshal signed tx", "err", err)
return common.Hash{}, 0, fmt.Errorf("failed to marshal signed tx, err: %w", err)
}
log.Info("--------------Morty------------", "rawTx", common.Bytes2Hex(rawTx))
if err := s.sendTransactionToMultipleClients(signedTx); err != nil {
// Delete the transaction from the pending transaction table if it fails to send.
if updateErr := s.pendingTransactionOrm.DeleteTransactionByTxHash(s.ctx, signedTx.Hash()); updateErr != nil {
@@ -586,6 +601,7 @@ func (s *Sender) createReplacingTransaction(tx *gethTypes.Transaction, baseFee,
// but don't exceed maxGasPrice
if gasFeeCap.Cmp(maxGasPrice) > 0 {
log.Info("adjusted gas fee cap to max gas price", "original", originalGasFeeCap.Uint64(), "gasFeeCap", gasFeeCap.Uint64(), "maxGasPrice", maxGasPrice.Uint64())
gasFeeCap = maxGasPrice
}
@@ -602,6 +618,7 @@ func (s *Sender) createReplacingTransaction(tx *gethTypes.Transaction, baseFee,
// but don't exceed maxBlobGasPrice
if blobGasFeeCap.Cmp(maxBlobGasPrice) > 0 {
log.Info("adjusted blob gas fee cap to max blob gas price", "original", originalBlobGasFeeCap.Uint64(), "blobGasFeeCap", blobGasFeeCap.Uint64(), "maxBlobGasPrice", maxBlobGasPrice.Uint64())
blobGasFeeCap = maxBlobGasPrice
}
@@ -678,6 +695,8 @@ func (s *Sender) checkPendingTransaction() {
receipt, err := s.client.TransactionReceipt(s.ctx, originalTx.Hash())
if err == nil { // tx confirmed.
if receipt.BlockNumber.Uint64() <= confirmed {
// Record metrics before updating the database
if dbTxErr := s.db.Transaction(func(dbTX *gorm.DB) error {
// Update the status of the transaction to TxStatusConfirmed.
if updateErr := s.pendingTransactionOrm.UpdateTransactionStatusByTxHash(s.ctx, originalTx.Hash(), types.TxStatusConfirmed, dbTX); updateErr != nil {

View File

@@ -19,6 +19,8 @@ type senderMetrics struct {
currentGasPrice *prometheus.GaugeVec
currentBlobGasFeeCap *prometheus.GaugeVec
currentGasLimit *prometheus.GaugeVec
txConfirmationLatency *prometheus.HistogramVec
txResendCount *prometheus.HistogramVec
}
var (

View File

@@ -251,3 +251,32 @@ func (o *PendingTransaction) GetMaxNonceBySenderAddress(ctx context.Context, sen
return result.Nonce, nil
}
// GetTransactionByHash retrieves a transaction by its hash.
func (o *PendingTransaction) GetTransactionByHash(ctx context.Context, hash common.Hash) (*PendingTransaction, error) {
var transaction PendingTransaction
db := o.db.WithContext(ctx)
db = db.Model(&PendingTransaction{})
db = db.Where("hash = ?", hash.String())
if err := db.First(&transaction).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("transaction not found with hash: %s", hash.String())
}
return nil, fmt.Errorf("failed to get transaction by hash, hash: %v, err: %w", hash, err)
}
return &transaction, nil
}
// CountTransactionsByContextIDAndNonce counts the number of transactions with the same context_id and nonce.
// This is useful for tracking how many times a transaction has been resent.
func (o *PendingTransaction) CountTransactionsByContextIDAndNonce(ctx context.Context, contextID string, nonce uint64) (int64, error) {
var count int64
db := o.db.WithContext(ctx)
db = db.Model(&PendingTransaction{})
db = db.Where("context_id = ?", contextID)
db = db.Where("nonce = ?", nonce)
if err := db.Count(&count).Error; err != nil {
return 0, fmt.Errorf("failed to count transactions by context_id and nonce, context_id: %s, nonce: %d, err: %w", contextID, nonce, err)
}
return count, nil
}