Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
cf064dd
begin tx recorder for mel
rauljordan Dec 11, 2025
d91e37e
fix recorder
rauljordan Dec 12, 2025
20436a8
add unit test for tx recorder
rauljordan Dec 12, 2025
bb28aeb
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
pmikolajczyk41 Dec 19, 2025
0d7e155
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Dec 23, 2025
c9ab0a0
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Dec 23, 2025
6f3c0f9
fix tx recorder
ganeshvanahalli Dec 23, 2025
02259cf
add changelog and fix lint
ganeshvanahalli Dec 23, 2025
a4318bd
Implement receipt recorder for mel validation
ganeshvanahalli Dec 30, 2025
b51c239
code refactor
ganeshvanahalli Dec 30, 2025
20ea831
Make tx and receipt fetcher in mel-replay to work with recorded preim…
ganeshvanahalli Jan 2, 2026
27e4d71
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Jan 5, 2026
e2b6866
Merge branch 'raul/mel-tx-record' into fetching-melobjectsfrom-preimages
ganeshvanahalli Jan 5, 2026
be5fd45
initial mel validator work
ganeshvanahalli Dec 17, 2025
37bce6b
refactor
ganeshvanahalli Dec 18, 2025
8b45fd3
add testing for correctness of preimages
ganeshvanahalli Dec 19, 2025
2cea9a2
add changelog
ganeshvanahalli Jan 5, 2026
2a6119d
minor fixes
ganeshvanahalli Jan 5, 2026
c5ce517
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Jan 5, 2026
4005e55
Merge branch 'raul/mel-tx-record' into fetching-melobjectsfrom-preimages
ganeshvanahalli Jan 5, 2026
c9c3163
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
9ab227c
remove debug statement
ganeshvanahalli Jan 5, 2026
8fe857e
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
063ce8e
refactor
ganeshvanahalli Jan 5, 2026
985ce53
Merge branch 'raul/mel-tx-record' into fetching-melobjectsfrom-preimages
ganeshvanahalli Jan 5, 2026
c19f416
code refactor
ganeshvanahalli Jan 5, 2026
4247212
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
9457d22
update impl of GetPreimages
ganeshvanahalli Jan 5, 2026
95214e7
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
10aa51d
correctly record preimages for relevant logs
ganeshvanahalli Jan 5, 2026
cec5783
reduce code diff
ganeshvanahalli Jan 5, 2026
1533fe9
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
c9c2421
fix test
ganeshvanahalli Jan 5, 2026
af7ef6d
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
5659d42
merge mel-txandreceipt-fetcher
ganeshvanahalli Jan 7, 2026
72e71bd
Merge branch 'mel-txandreceipt-fetcher' into mel-validator-createvali…
ganeshvanahalli Jan 7, 2026
0346813
Merge branch 'mel-txandreceipt-fetcher' into mel-validator-createvali…
ganeshvanahalli Jan 7, 2026
71a8ac5
Merge branch 'mel-txandreceipt-fetcher' into mel-validator-createvali…
ganeshvanahalli Jan 7, 2026
1ab38f9
use typeBasedPreimageResolver in test and update melValidator
ganeshvanahalli Jan 7, 2026
19da777
merge master and resolve conflicts
ganeshvanahalli Jan 9, 2026
9b7a73c
implement more methods of MEL validator and make blockvalidator to on…
ganeshvanahalli Jan 14, 2026
caf8009
update mel state hash method
ganeshvanahalli Jan 16, 2026
e44caf4
Merge branch 'raul/mel-inbox-reading' into mel-validator-createvalida…
ganeshvanahalli Jan 20, 2026
a6b5f50
reduce diff
ganeshvanahalli Jan 20, 2026
874fa8e
Merge branch 'raul/mel-inbox-reading' into mel-validator-createvalida…
ganeshvanahalli Jan 20, 2026
37d3096
Merge branch 'raul/mel-inbox-reading' into mel-validator-createvalida…
ganeshvanahalli Jan 21, 2026
73e5ce2
Merge branch 'raul/mel-inbox-reading' into mel-validator-createvalida…
ganeshvanahalli Jan 21, 2026
cf044ee
Merge branch 'raul/mel-inbox-reading' into mel-validator-createvalida…
ganeshvanahalli Jan 21, 2026
767f30d
Merge branch 'raul/mel-inbox-reading' into mel-validator-createvalida…
ganeshvanahalli Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions arbnode/mel/runner/mel.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ func (m *MessageExtractor) GetHeadState(ctx context.Context) (*mel.State, error)
return m.melDB.GetHeadMelState(ctx)
}

func (m *MessageExtractor) GetState(ctx context.Context, parentchainBlocknumber uint64) (*mel.State, error) {
return m.melDB.State(ctx, parentchainBlocknumber)
}

func (m *MessageExtractor) GetMsgCount(ctx context.Context) (arbutil.MessageIndex, error) {
headState, err := m.melDB.GetHeadMelState(ctx)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions changelog/ganeshvanahalli-nit-4142.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Added
- Introduces MEL validator
6 changes: 3 additions & 3 deletions cmd/nitro/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (
"github.com/offchainlabs/nitro/cmd/staterecovery"
"github.com/offchainlabs/nitro/execution/gethexec"
"github.com/offchainlabs/nitro/solgen/go/rollupgen"
"github.com/offchainlabs/nitro/staker/bold"
"github.com/offchainlabs/nitro/staker"
"github.com/offchainlabs/nitro/statetransfer"
"github.com/offchainlabs/nitro/util"
"github.com/offchainlabs/nitro/util/arbmath"
Expand Down Expand Up @@ -957,7 +957,7 @@ func validateGenesisAssertion(ctx context.Context, rollupAddress common.Address,
if err != nil {
return err
}
genesisAssertionCreationInfo, err := bold.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
genesisAssertionCreationInfo, err := staker.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
if err != nil {
// If we can't find the empty genesis assertion, try to compute the assertion for non-empty genesis
genesisGlobalState := protocol.GoGlobalState{
Expand All @@ -978,7 +978,7 @@ func validateGenesisAssertion(ctx context.Context, rollupAddress common.Address,
if err != nil {
return err
}
genesisAssertionCreationInfo, err = bold.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
genesisAssertionCreationInfo, err = staker.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
if err != nil {
return err
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/pruning/pruning.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"github.com/offchainlabs/nitro/solgen/go/bridgegen"
"github.com/offchainlabs/nitro/solgen/go/rollupgen"
"github.com/offchainlabs/nitro/staker"
"github.com/offchainlabs/nitro/staker/bold"
legacystaker "github.com/offchainlabs/nitro/staker/legacy"
multiprotocolstaker "github.com/offchainlabs/nitro/staker/multi_protocol"
)
Expand Down Expand Up @@ -260,7 +259,7 @@ func getLatestConfirmedHash(ctx context.Context, rollupAddrs chaininfo.RollupAdd
if err != nil {
return common.Hash{}, err
}
assertion, err := bold.ReadBoldAssertionCreationInfo(
assertion, err := staker.ReadBoldAssertionCreationInfo(
ctx,
rollupUserLogic,
l1Client,
Expand Down
13 changes: 12 additions & 1 deletion staker/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func NewThrottledValidationSpawner(spawner validator.ValidationSpawner) *Throttl
type BlockValidator struct {
stopwaiter.StopWaiter
*StatelessBlockValidator
melValidator MELValidatorInterface

reorgMutex sync.RWMutex

Expand Down Expand Up @@ -636,14 +637,24 @@ func (v *BlockValidator) createNextValidationEntry(ctx context.Context) (bool, e
log.Trace("create validation entry: nothing to do", "pos", pos, "validated", v.validated())
return false, nil
}
streamerMsgCount, err := v.streamer.GetProcessedMessageCount()
streamerMsgCount, err := v.streamer.GetProcessedMessageCount() // Ask MEL validator LatestValidatedMELState().MsgCount
if err != nil {
return false, err
}
if pos >= streamerMsgCount {
log.Trace("create validation entry: nothing to do", "pos", pos, "streamerMsgCount", streamerMsgCount)
return false, nil
}
if v.melValidator != nil {
latestValidatedState, err := v.melValidator.LatestValidatedMELState(ctx)
if err != nil {
return false, err
}
if pos >= arbutil.MessageIndex(latestValidatedState.MsgCount) {
log.Trace("create validation entry: nothing to do", "pos", pos, "latestMELValidatedMsgCount", latestValidatedState.MsgCount)
return false, nil
}
}
msg, err := v.streamer.GetMessage(pos)
if err != nil {
return false, err
Expand Down
94 changes: 1 addition & 93 deletions staker/bold/bold_staker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import (
"context"
"errors"
"fmt"
"math/big"
"strings"
"time"

"github.com/spf13/pflag"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -45,20 +43,6 @@ var (
boldStakerAmountStakedGauge = metrics.GetOrRegisterGauge("arb/staker/amount_staked", nil)
)

var assertionCreatedId common.Hash

func init() {
rollupAbi, err := rollupgen.RollupCoreMetaData.GetAbi()
if err != nil {
panic(err)
}
assertionCreatedEvent, ok := rollupAbi.Events["AssertionCreated"]
if !ok {
panic("RollupCore ABI missing AssertionCreated event")
}
assertionCreatedId = assertionCreatedEvent.ID
}

type BoldConfig struct {
// How often to post assertions onchain.
AssertionPostingInterval time.Duration `koanf:"assertion-posting-interval"`
Expand Down Expand Up @@ -290,7 +274,7 @@ func (b *BOLDStaker) Initialize(ctx context.Context) error {
}
latestStaked = latestConfirmed
}
assertion, err := ReadBoldAssertionCreationInfo(
assertion, err := staker.ReadBoldAssertionCreationInfo(
ctx,
rollupUserLogic,
b.client,
Expand Down Expand Up @@ -629,79 +613,3 @@ func newBOLDChallengeManager(
}
return manager, nil
}

// Read the creation info for an assertion by looking up its creation
// event from the rollup contracts.
func ReadBoldAssertionCreationInfo(
ctx context.Context,
rollup *rollupgen.RollupUserLogic,
client bind.ContractBackend,
rollupAddress common.Address,
assertionHash common.Hash,
) (*protocol.AssertionCreatedInfo, error) {
var creationBlock uint64
var topics [][]common.Hash
if assertionHash == (common.Hash{}) {
rollupDeploymentBlock, err := rollup.RollupDeploymentBlock(&bind.CallOpts{Context: ctx})
if err != nil {
return nil, err
}
if !rollupDeploymentBlock.IsUint64() {
return nil, errors.New("rollup deployment block was not a uint64")
}
creationBlock = rollupDeploymentBlock.Uint64()
} else {
var b [32]byte
copy(b[:], assertionHash[:])
assertionCreationBlock, err := rollup.GetAssertionCreationBlockForLogLookup(&bind.CallOpts{Context: ctx}, b)
if err != nil {
return nil, err
}
if !assertionCreationBlock.IsUint64() {
return nil, errors.New("assertion creation block was not a uint64")
}
creationBlock = assertionCreationBlock.Uint64()
}
topics = [][]common.Hash{{assertionCreatedId}, {assertionHash}}
var query = ethereum.FilterQuery{
FromBlock: new(big.Int).SetUint64(creationBlock),
ToBlock: new(big.Int).SetUint64(creationBlock),
Addresses: []common.Address{rollupAddress},
Topics: topics,
}
logs, err := client.FilterLogs(ctx, query)
if err != nil {
return nil, err
}
if len(logs) == 0 {
return nil, errors.New("no assertion creation logs found")
}
if len(logs) > 1 {
return nil, errors.New("found multiple instances of requested node")
}
ethLog := logs[0]
parsedLog, err := rollup.ParseAssertionCreated(ethLog)
if err != nil {
return nil, err
}
afterState := parsedLog.Assertion.AfterState
creationL1Block, err := arbutil.CorrespondingL1BlockNumber(ctx, client, ethLog.BlockNumber)
if err != nil {
return nil, err
}
return &protocol.AssertionCreatedInfo{
ConfirmPeriodBlocks: parsedLog.ConfirmPeriodBlocks,
RequiredStake: parsedLog.RequiredStake,
ParentAssertionHash: protocol.AssertionHash{Hash: parsedLog.ParentAssertionHash},
BeforeState: parsedLog.Assertion.BeforeState,
AfterState: afterState,
InboxMaxCount: parsedLog.InboxMaxCount,
AfterInboxBatchAcc: parsedLog.AfterInboxBatchAcc,
AssertionHash: protocol.AssertionHash{Hash: parsedLog.AssertionHash},
WasmModuleRoot: parsedLog.WasmModuleRoot,
ChallengeManager: parsedLog.ChallengeManager,
TransactionHash: ethLog.TxHash,
CreationParentBlock: ethLog.BlockNumber,
CreationL1Block: creationL1Block,
}, nil
}
105 changes: 105 additions & 0 deletions staker/bold_assertioncreation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package staker

import (
"context"
"errors"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind/v2"
"github.com/ethereum/go-ethereum/common"

"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/bold/protocol"
"github.com/offchainlabs/nitro/solgen/go/rollupgen"
)

var assertionCreatedId common.Hash

func init() {
rollupAbi, err := rollupgen.RollupCoreMetaData.GetAbi()
if err != nil {
panic(err)
}
assertionCreatedEvent, ok := rollupAbi.Events["AssertionCreated"]
if !ok {
panic("RollupCore ABI missing AssertionCreated event")
}
assertionCreatedId = assertionCreatedEvent.ID
}

// Read the creation info for an assertion by looking up its creation
// event from the rollup contracts.
func ReadBoldAssertionCreationInfo(
ctx context.Context,
rollup *rollupgen.RollupUserLogic,
client bind.ContractBackend,
rollupAddress common.Address,
assertionHash common.Hash,
) (*protocol.AssertionCreatedInfo, error) {
var creationBlock uint64
var topics [][]common.Hash
if assertionHash == (common.Hash{}) {
rollupDeploymentBlock, err := rollup.RollupDeploymentBlock(&bind.CallOpts{Context: ctx})
if err != nil {
return nil, err
}
if !rollupDeploymentBlock.IsUint64() {
return nil, errors.New("rollup deployment block was not a uint64")
}
creationBlock = rollupDeploymentBlock.Uint64()
} else {
var b [32]byte
copy(b[:], assertionHash[:])
assertionCreationBlock, err := rollup.GetAssertionCreationBlockForLogLookup(&bind.CallOpts{Context: ctx}, b)
if err != nil {
return nil, err
}
if !assertionCreationBlock.IsUint64() {
return nil, errors.New("assertion creation block was not a uint64")
}
creationBlock = assertionCreationBlock.Uint64()
}
topics = [][]common.Hash{{assertionCreatedId}, {assertionHash}}
var query = ethereum.FilterQuery{
FromBlock: new(big.Int).SetUint64(creationBlock),
ToBlock: new(big.Int).SetUint64(creationBlock),
Addresses: []common.Address{rollupAddress},
Topics: topics,
}
logs, err := client.FilterLogs(ctx, query)
if err != nil {
return nil, err
}
if len(logs) == 0 {
return nil, errors.New("no assertion creation logs found")
}
if len(logs) > 1 {
return nil, errors.New("found multiple instances of requested node")
}
ethLog := logs[0]
parsedLog, err := rollup.ParseAssertionCreated(ethLog)
if err != nil {
return nil, err
}
afterState := parsedLog.Assertion.AfterState
creationL1Block, err := arbutil.CorrespondingL1BlockNumber(ctx, client, ethLog.BlockNumber)
if err != nil {
return nil, err
}
return &protocol.AssertionCreatedInfo{
ConfirmPeriodBlocks: parsedLog.ConfirmPeriodBlocks,
RequiredStake: parsedLog.RequiredStake,
ParentAssertionHash: protocol.AssertionHash{Hash: parsedLog.ParentAssertionHash},
BeforeState: parsedLog.Assertion.BeforeState,
AfterState: afterState,
InboxMaxCount: parsedLog.InboxMaxCount,
AfterInboxBatchAcc: parsedLog.AfterInboxBatchAcc,
AssertionHash: protocol.AssertionHash{Hash: parsedLog.AssertionHash},
WasmModuleRoot: parsedLog.WasmModuleRoot,
ChallengeManager: parsedLog.ChallengeManager,
TransactionHash: ethLog.TxHash,
CreationParentBlock: ethLog.BlockNumber,
CreationL1Block: creationL1Block,
}, nil
}
Loading
Loading