Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions arbnode/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ func getStaker(
blockValidator *staker.BlockValidator,
dapRegistry *daprovider.DAProviderRegistry,
messageExtractor *melrunner.MessageExtractor,
melValidator *staker.MELValidator,
) (*multiprotocolstaker.MultiProtocolStaker, *MessagePruner, common.Address, error) {
var stakerObj *multiprotocolstaker.MultiProtocolStaker
var messagePruner *MessagePruner
Expand Down Expand Up @@ -1153,7 +1154,7 @@ func getStaker(
if tracker == nil || reader == nil {
return nil, nil, common.Address{}, errors.New("staker requires either message extractor or inbox tracker/reader")
}
stakerObj, err = multiprotocolstaker.NewMultiProtocolStaker(stack, l1Reader, wallet, bind.CallOpts{}, func() *legacystaker.L1ValidatorConfig { return &configFetcher.Get().Staker }, &configFetcher.Get().Bold, blockValidator, statelessBlockValidator, nil, deployInfo.StakeToken, deployInfo.Rollup, confirmedNotifiers, deployInfo.ValidatorUtils, deployInfo.Bridge, txStreamer, tracker, reader, dapRegistry, fatalErrChan)
stakerObj, err = multiprotocolstaker.NewMultiProtocolStaker(stack, l1Reader, wallet, bind.CallOpts{}, func() *legacystaker.L1ValidatorConfig { return &configFetcher.Get().Staker }, &configFetcher.Get().Bold, blockValidator, statelessBlockValidator, nil, deployInfo.StakeToken, deployInfo.Rollup, confirmedNotifiers, deployInfo.ValidatorUtils, deployInfo.Bridge, txStreamer, tracker, reader, dapRegistry, fatalErrChan, melValidator)
if err != nil {
return nil, nil, common.Address{}, err
}
Expand Down Expand Up @@ -1551,7 +1552,7 @@ func createNodeImpl(
batchMetaFetcher = messageExtractor
}

stakerObj, messagePruner, stakerAddr, err := getStaker(ctx, config, configFetcher, consensusDB, l1Reader, txOptsValidator, syncMonitor, parentChain, l1client, deployInfo, txStreamer, validatorInboxReader, validatorInboxTracker, batchMetaFetcher, stack, fatalErrChan, statelessBlockValidator, blockValidator, dapRegistry, messageExtractor)
stakerObj, messagePruner, stakerAddr, err := getStaker(ctx, config, configFetcher, consensusDB, l1Reader, txOptsValidator, syncMonitor, parentChain, l1client, deployInfo, txStreamer, validatorInboxReader, validatorInboxTracker, batchMetaFetcher, stack, fatalErrChan, statelessBlockValidator, blockValidator, dapRegistry, messageExtractor, melValidator)
if err != nil {
return nil, err
}
Expand Down
33 changes: 32 additions & 1 deletion bold/assertions/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
chain protocol.AssertionChain
backend protocol.ChainBackend
execProvider state.ExecutionProvider
melLookup state.ValidatedMELStateLookup // nil when MEL is not active
times timings
rollupAddr common.Address
validatorName string
Expand Down Expand Up @@ -111,6 +112,15 @@
}
}

// WithMELStateLookup enables MEL-based assertion determinism. When set,
// assertions use NextParentChainBlockHash instead of InboxMaxCount.
func WithMELStateLookup(lookup state.ValidatedMELStateLookup) Opt {
return func(m *Manager) {
m.melLookup = lookup
}
}


Check failure on line 123 in bold/assertions/manager.go

View workflow job for this annotation

GitHub Actions / fast / Lint and Build

File is not properly formatted (gci)
func WithFastConfirmation() Opt {
return func(m *Manager) {
m.enableFastConfirmation = true
Expand Down Expand Up @@ -383,7 +393,28 @@

func (m *Manager) ExecutionStateAfterParent(ctx context.Context, parentInfo *protocol.AssertionCreatedInfo) (*protocol.ExecutionState, error) {
goGlobalState := protocol.GoGlobalStateFromSolidity(parentInfo.AfterState.GlobalState)
return m.execProvider.ExecutionStateAfterPreviousState(ctx, parentInfo.InboxMaxCount.Uint64(), goGlobalState)
batchCount, err := m.batchCountFromAssertion(ctx, parentInfo)
if err != nil {
return nil, err
}
return m.execProvider.ExecutionStateAfterPreviousState(ctx, batchCount, goGlobalState)
}

// batchCountFromAssertion determines the batch count for the next assertion.
// Post-MEL (melLookup != nil): derives it from NextParentChainBlockHash via validated MEL state.
// Pre-MEL: uses InboxMaxCount directly.
func (m *Manager) batchCountFromAssertion(ctx context.Context, info *protocol.AssertionCreatedInfo) (uint64, error) {
if m.melLookup != nil {
melInfo, err := m.melLookup.GetValidatedMELStateByBlockHash(ctx, info.NextParentChainBlockHash)
if err != nil {
return 0, err
}
return melInfo.BatchCount, nil
}
if !info.InboxMaxCount.IsUint64() {
return 0, errors.New("inbox max count not a uint64")
}
return info.InboxMaxCount.Uint64(), nil
}

func (m *Manager) ForksDetected() uint64 {
Expand Down
36 changes: 23 additions & 13 deletions bold/assertions/poster.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,33 +186,43 @@ func (m *Manager) PostAssertionBasedOnParent(
) (protocol.Assertion, error),
) (option.Option[protocol.Assertion], error) {
none := option.None[protocol.Assertion]()
if !parentCreationInfo.InboxMaxCount.IsUint64() {
return none, errors.New("inbox max count not a uint64")
}
// The parent assertion tells us what the next posted assertion's batch should be.
// We read this value and use it to compute the required execution state we must post.
batchCount := parentCreationInfo.InboxMaxCount.Uint64()
parentBlockHash := protocol.GoGlobalStateFromSolidity(parentCreationInfo.AfterState.GlobalState).BlockHash
newState, err := m.ExecutionStateAfterParent(ctx, parentCreationInfo)
// Derive the batch count from the parent assertion. Post-MEL this uses
// NextParentChainBlockHash via validated MEL state; pre-MEL it uses InboxMaxCount.
batchCount, err := m.batchCountFromAssertion(ctx, parentCreationInfo)
if err != nil {
if errors.Is(err, state.ErrChainCatchingUp) {
chainCatchingUpCounter.Inc(1)
parentBlockHash := protocol.GoGlobalStateFromSolidity(parentCreationInfo.AfterState.GlobalState).BlockHash
log.Info(
"Waiting for more batches to post next assertion",
"latestStakedAssertionBatchCount", batchCount,
"latestStakedAssertionBlockHash", containers.Trunc(parentBlockHash[:]),
)
// If the chain is catching up, we wait for a bit and try again.
time.Sleep(m.times.avgBlockTime / 10)
return none, nil
}
return none, errors.Wrapf(err, "could not get execution state at batch count %d with parent block hash %v", batchCount, parentBlockHash)
return none, errors.Wrapf(err, "could not derive batch count from parent assertion")
}
newState, err := m.ExecutionStateAfterParent(ctx, parentCreationInfo)
if err != nil {
if errors.Is(err, state.ErrChainCatchingUp) {
chainCatchingUpCounter.Inc(1)
log.Info(
"Waiting for execution state to catch up",
"batchCount", batchCount,
)
time.Sleep(m.times.avgBlockTime / 10)
return none, nil
}
return none, errors.Wrapf(err, "could not get execution state at batch count %d", batchCount)
}

// If the assertion is not an overflow assertion i.e !(newState.GlobalState.Batch < batchCount) derived from
// contracts check for overflow assertion => assertion.afterState.globalState.u64Vals[0] < assertion.beforeStateData.configData.nextInboxPosition)
// then should check if we need to wait for the minimum number of blocks between assertions and a minimum time since parent assertion creation.
// Overflow ones are not subject to this check onchain.
// Note: post-MEL, overflow assertions are removed from the contracts, but we keep this check
// for backwards compatibility with pre-MEL chains.
isOverflowAssertion := newState.MachineStatus != protocol.MachineStatusErrored && newState.GlobalState.Batch < batchCount
if !isOverflowAssertion {
if err = m.waitToPostIfNeeded(ctx, parentCreationInfo); err != nil {
Expand All @@ -221,8 +231,8 @@ func (m *Manager) PostAssertionBasedOnParent(
}

log.Info(
"Posting assertion for batch we agree with",
"requiredInboxMaxCount", batchCount,
"Posting assertion",
"batchCount", batchCount,
"validatorName", m.validatorName,
)
assertion, err := submitFn(
Expand All @@ -241,7 +251,7 @@ func (m *Manager) PostAssertionBasedOnParent(
assertionPostedCounter.Inc(1)
log.Info("Successfully submitted assertion",
"validatorName", m.validatorName,
"requiredInboxMaxCount", batchCount,
"batchCount", batchCount,
"postedExecutionState", fmt.Sprintf("%+v", newState),
"assertionHash", assertion.Id(),
)
Expand Down
9 changes: 9 additions & 0 deletions bold/challenge/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
assertionManager AssertionManager
watcher *chain.Watcher
stateManager state.Provider
melLookup state.ValidatedMELStateLookup // nil when MEL is not active
name string
headerProvider HeaderProvider
timeRef clock.Reference
Expand Down Expand Up @@ -110,6 +111,14 @@
}
}

// WithMELStateLookup enables MEL-based determinism for challenge metadata.
func WithMELStateLookup(lookup state.ValidatedMELStateLookup) Opt {
return func(val *Manager) {
val.melLookup = lookup
}
}


Check failure on line 121 in bold/challenge/manager.go

View workflow job for this annotation

GitHub Actions / fast / Lint and Build

File is not properly formatted (gci)
// New sets up a challenge manager instance provided a protocol, state manager,
// chain watcher, assertion manager, and additional options.
func New(
Expand Down
15 changes: 15 additions & 0 deletions bold/challenge/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type stackParams struct {
delegatedStaking bool
autoDeposit bool
autoAllowanceApproval bool
melLookup state.ValidatedMELStateLookup
}

var defaultStackParams = stackParams{
Expand Down Expand Up @@ -191,6 +192,14 @@ func OverrideAssertionManager(asm *assertions.Manager) StackOpt {
}
}

// StackWithMELStateLookup enables MEL-based assertion determinism for both the
// assertion manager and challenge manager in the stack.
func StackWithMELStateLookup(lookup state.ValidatedMELStateLookup) StackOpt {
return func(p *stackParams) {
p.melLookup = lookup
}
}

// NewChallengeStack creates a new ChallengeManager and all of the dependencies
// wiring them together.
func NewChallengeStack(
Expand Down Expand Up @@ -270,6 +279,9 @@ func NewChallengeStack(
if !params.autoAllowanceApproval {
amOpts = append(amOpts, assertions.WithoutAutoAllowanceApproval())
}
if params.melLookup != nil {
amOpts = append(amOpts, assertions.WithMELStateLookup(params.melLookup))
}
asm, err = assertions.NewManager(
parent,
provider,
Expand All @@ -292,6 +304,9 @@ func NewChallengeStack(
if params.headerProvider != nil {
cmOpts = append(cmOpts, WithHeaderProvider(params.headerProvider))
}
if params.melLookup != nil {
cmOpts = append(cmOpts, WithMELStateLookup(params.melLookup))
}
if params.apiAddr != "" {
cmOpts = append(cmOpts, WithAPIServer(api))
}
Expand Down
29 changes: 22 additions & 7 deletions bold/protocol/execution_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ type GoGlobalState struct {

func GoGlobalStateFromSolidity(globalState rollupgen.GlobalState) GoGlobalState {
return GoGlobalState{
BlockHash: globalState.Bytes32Vals[0],
SendRoot: globalState.Bytes32Vals[1],
Batch: globalState.U64Vals[0],
PosInBatch: globalState.U64Vals[1],
BlockHash: globalState.Bytes32Vals[0],
SendRoot: globalState.Bytes32Vals[1],
MELStateHash: globalState.Bytes32Vals[2],
MELMsgHash: globalState.Bytes32Vals[3],
Batch: globalState.U64Vals[0],
PosInBatch: globalState.U64Vals[1],
}
}

Expand All @@ -48,16 +50,29 @@ func ComputeSimpleMachineChallengeHash(

func (s GoGlobalState) Hash() common.Hash {
data := []byte("Global state:")
data = append(data, s.BlockHash.Bytes()...)
data = append(data, s.SendRoot.Bytes()...)
// Include bytes32 values up to the last non-zero index, with a minimum of
// index 1 (BlockHash + SendRoot always included). This matches the Rust
// prover's bytes32_last_non_zero_index() for backwards compatibility:
// pre-MEL states (MEL fields zero) produce the same hash as before.
bytes32Vals := [4]common.Hash{s.BlockHash, s.SendRoot, s.MELStateHash, s.MELMsgHash}
endIdx := 1 // always include at least BlockHash and SendRoot
for i := len(bytes32Vals) - 1; i > 1; i-- {
if bytes32Vals[i] != (common.Hash{}) {
endIdx = i
break
}
}
for i := 0; i <= endIdx; i++ {
data = append(data, bytes32Vals[i].Bytes()...)
}
data = append(data, u64ToBe(s.Batch)...)
data = append(data, u64ToBe(s.PosInBatch)...)
return crypto.Keccak256Hash(data)
}

func (s GoGlobalState) AsSolidityStruct() challengeV2gen.GlobalState {
return challengeV2gen.GlobalState{
Bytes32Vals: [2][32]byte{s.BlockHash, s.SendRoot},
Bytes32Vals: [4][32]byte{s.BlockHash, s.SendRoot, s.MELStateHash, s.MELMsgHash},
U64Vals: [2]uint64{s.Batch, s.PosInBatch},
}
}
Expand Down
19 changes: 11 additions & 8 deletions bold/protocol/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,17 @@ type AssertionCreatedInfo struct {
ParentAssertionHash AssertionHash
BeforeState rollupgen.AssertionState
AfterState rollupgen.AssertionState
InboxMaxCount *big.Int
AfterInboxBatchAcc common.Hash
AssertionHash AssertionHash
WasmModuleRoot common.Hash
ChallengeManager common.Address
TransactionHash common.Hash
CreationParentBlock uint64
CreationL1Block uint64
// Pre-MEL: InboxMaxCount is the batch count the next assertion must consume.
// Post-MEL: InboxMaxCount is unused (zero); NextParentChainBlockHash is used instead.
InboxMaxCount *big.Int
AfterInboxBatchAcc common.Hash
NextParentChainBlockHash common.Hash
AssertionHash AssertionHash
WasmModuleRoot common.Hash
ChallengeManager common.Address
TransactionHash common.Hash
CreationParentBlock uint64
CreationL1Block uint64
}

func (i AssertionCreatedInfo) ExecutionHash() common.Hash {
Expand Down
32 changes: 15 additions & 17 deletions bold/protocol/sol/assertion_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,12 +555,11 @@ func (a *AssertionChain) NewStakeOnNewAssertion(
assertionInputs rollupgen.AssertionInputs,
expectedAssertionHash [32]byte,
) (*types.Transaction, error) {
return a.userLogic.NewStakeOnNewAssertion611c3d80(
return a.userLogic.NewStakeOnNewAssertion7f62c2af(
opts,
tokenAmount,
assertionInputs,
expectedAssertionHash,
a.withdrawalAddress,
)
}
return a.createAndStakeOnAssertion(
Expand Down Expand Up @@ -1073,21 +1072,20 @@ func (a *AssertionChain) ReadAssertionCreationInfo(
}
creationL1Block := res.CreatedAtBlock
return &protocol.AssertionCreatedInfo{
ConfirmPeriodBlocks: parsedLog.ConfirmPeriodBlocks,
RequiredStake: parsedLog.RequiredStake,
ParentAssertionHash: protocol.AssertionHash{Hash: parsedLog.ParentAssertionHash},
BeforeState: parsedLog.Assertion.BeforeState,
AfterState: afterState,
// PR 427: InboxMaxCount / AfterInboxBatchAcc dropped from AssertionCreated event.
// Left zero pending follow-up PR that rewires against NextParentChainBlockHash.
InboxMaxCount: big.NewInt(0),
AfterInboxBatchAcc: common.Hash{},
AssertionHash: protocol.AssertionHash{Hash: parsedLog.AssertionHash},
WasmModuleRoot: parsedLog.WasmModuleRoot,
ChallengeManager: parsedLog.ChallengeManager,
TransactionHash: ethLog.TxHash,
CreationParentBlock: ethLog.BlockNumber,
CreationL1Block: creationL1Block,
ConfirmPeriodBlocks: parsedLog.ConfirmPeriodBlocks,
RequiredStake: parsedLog.RequiredStake,
ParentAssertionHash: protocol.AssertionHash{Hash: parsedLog.ParentAssertionHash},
BeforeState: parsedLog.Assertion.BeforeState,
AfterState: afterState,
InboxMaxCount: big.NewInt(0),
AfterInboxBatchAcc: common.Hash{},
NextParentChainBlockHash: parsedLog.NextParentChainBlockHash,
AssertionHash: protocol.AssertionHash{Hash: parsedLog.AssertionHash},
WasmModuleRoot: parsedLog.WasmModuleRoot,
ChallengeManager: parsedLog.ChallengeManager,
TransactionHash: ethLog.TxHash,
CreationParentBlock: ethLog.BlockNumber,
CreationL1Block: creationL1Block,
}, nil
}

Expand Down
30 changes: 25 additions & 5 deletions bold/state/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ import (

var ErrChainCatchingUp = errors.New("chain is catching up to the execution state")

// MELStateInfo contains the key fields from a validated MEL state
// needed by the BOLD assertion and challenge system.
type MELStateInfo struct {
BatchCount uint64
MsgCount uint64
ParentChainBlockNumber uint64
StateHash common.Hash
}

// ValidatedMELStateLookup translates a parent chain block hash (the post-MEL
// determinism rule) into MEL batch/message counts. It only returns data for
// states that have been validated by the MEL validator.
type ValidatedMELStateLookup interface {
// GetValidatedMELStateByBlockHash returns MEL state info at the given
// parent chain block hash. Returns ErrChainCatchingUp if MEL validation
// hasn't reached this block yet.
GetValidatedMELStateByBlockHash(ctx context.Context, blockHash common.Hash) (*MELStateInfo, error)
}

// Batch index for an Arbitrum L2 state.
type Batch uint64

Expand All @@ -36,11 +55,12 @@ type StepSize uint64

// ConfigSnapshot for an assertion on Arbitrum.
type ConfigSnapshot struct {
RequiredStake *big.Int
ChallengeManagerAddress common.Address
ConfirmPeriodBlocks uint64
WasmModuleRoot [32]byte
InboxMaxCount *big.Int
RequiredStake *big.Int
ChallengeManagerAddress common.Address
ConfirmPeriodBlocks uint64
WasmModuleRoot [32]byte
InboxMaxCount *big.Int // Pre-MEL determinism rule.
NextParentChainBlockHash common.Hash // Post-MEL determinism rule.
}

type History struct {
Expand Down
Loading
Loading