Skip to content

Conversation

@varun-doshi
Copy link

Closes: Asana Task

This PR:

  • introduces a new struct EspressoBatchPosterConfig which hold Espresso fields for the Batch Poster. This separates the general configs from espresso specific ones.
  • BatchPoster has a new field EspressoBatchConfigFetcher which is an interface similar to BatchConfigFetcher
  • introduces a new struct EspressoConfig which will hold Espresso specific configs and fields. Currently, it holds EspressoCaffNodeConfig and EspressoBatchPosterConfig
  • the Node Config now has a structure like this:
{
  // original upstream fields
  espresso : {
       espresso specific fields
      }
}
  • Added a small test to check this parsing TestEspressoConfigParsing

End result of this PR should be that we can have something similar to this as the config file:

{
"parent-chain": {
    "connection": {
      "url": "${parent_chain_url_secret_arn}"
    }
  },
  "chain": {
    "info-files": ["/config/l2_chain_info.json"]
  },
"node": {...},
"espresso": {...},
}

Key places to review:

  • arbnode/batch_poster.go
  • arbnode/node.go

Testing

  • Most local tests are passing other than the nitro-node e2e test. I believe that one is failing because I haven't changed the config.ts file inside nitro-node (please correct me if I'm wrong on this)

What is left:

  • There are some commented out code which I will remove
  • Possibly if there are other fields that need to be added to the EspressoConfig

MinimumHotshotBlockNum uint64 `koanf:"minimum-hotshot-block-num"`
}

type EspressoBatchPosterConfig struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this to a separate file? Maybe under the espresso folder and then just import here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think moving this will help minimize merge conflicts in the batch_poster.go file, Additionally, it'll make git diffs cleaner for future version updates.

DangerousBatchPosterConfigAddOptions(prefix+".dangerous", f)
}

func EspressoBatchPosterConfigAddOptions(prefix string, f *pflag.FlagSet) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this

@varun-doshi varun-doshi marked this pull request as ready for review January 13, 2026 05:35
@varun-doshi varun-doshi marked this pull request as draft January 13, 2026 05:35
@github-actions
Copy link

github-actions bot commented Jan 13, 2026

❌ 4 Tests Failed:

Tests completed Failed Passed Skipped
368 4 364 0
View the top 3 failed tests by shortest run time
TestUnsafeStakerConfig
Stack Traces | 0.010s run time
=== RUN   TestUnsafeStakerConfig
    config_test.go:57: goroutine 26 [running]:
        runtime/debug.Stack()
        	/opt/hostedtoolcache/go/1.25.5/x64/src/runtime/debug/stack.go:26 +0x5e
        github.com/offchainlabs/nitro/util/testhelpers.RequireImpl({0x2ff6330, 0xc000002000}, {0x2fcc880, 0xc0006119f8}, {0x0, 0x0, 0x0})
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/util/testhelpers/testhelpers.go:29 +0x55
        github.com/offchainlabs/nitro/cmd/nitro.Require(0xc000002000, {0x2fcc880, 0xc0006119f8}, {0x0, 0x0, 0x0})
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/cmd/nitro/config_test.go:277 +0x5d
        github.com/offchainlabs/nitro/cmd/nitro.TestUnsafeStakerConfig(0xc000002000)
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/cmd/nitro/config_test.go:57 +0x6d
        testing.tRunner(0xc000002000, 0x2da8990)
        	/opt/hostedtoolcache/go/1.25.5/x64/src/testing/testing.go:1934 +0xea
        created by testing.(*T).Run in goroutine 1
        	/opt/hostedtoolcache/go/1.25.5/x64/src/testing/testing.go:1997 +0x465
        
    config_test.go:57: �[31;1m [] 2 error(s) decoding:
        
        * 'node.batch-poster' has invalid keys: espresso-register-service-config
        * 'node.espresso' has invalid keys: espresso-batch-poster, espresso-caff-node, espresso-streamer �[0;0m
--- FAIL: TestUnsafeStakerConfig (0.01s)
TestUnsafeStakerConfig
Stack Traces | 0.010s run time
=== RUN   TestUnsafeStakerConfig
    config_test.go:57: goroutine 11 [running]:
        runtime/debug.Stack()
        	/opt/hostedtoolcache/go/1.25.5/x64/src/runtime/debug/stack.go:26 +0x5e
        github.com/offchainlabs/nitro/util/testhelpers.RequireImpl({0x2ff6330, 0xc000503c00}, {0x2fcc880, 0xc0009848e8}, {0x0, 0x0, 0x0})
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/util/testhelpers/testhelpers.go:29 +0x55
        github.com/offchainlabs/nitro/cmd/nitro.Require(0xc000503c00, {0x2fcc880, 0xc0009848e8}, {0x0, 0x0, 0x0})
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/cmd/nitro/config_test.go:277 +0x5d
        github.com/offchainlabs/nitro/cmd/nitro.TestUnsafeStakerConfig(0xc000503c00)
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/cmd/nitro/config_test.go:57 +0x6d
        testing.tRunner(0xc000503c00, 0x2da8990)
        	/opt/hostedtoolcache/go/1.25.5/x64/src/testing/testing.go:1934 +0xea
        created by testing.(*T).Run in goroutine 1
        	/opt/hostedtoolcache/go/1.25.5/x64/src/testing/testing.go:1997 +0x465
        
    config_test.go:57: �[31;1m [] 2 error(s) decoding:
        
        * 'node.batch-poster' has invalid keys: espresso-register-service-config
        * 'node.espresso' has invalid keys: espresso-batch-poster, espresso-caff-node, espresso-streamer �[0;0m
--- FAIL: TestUnsafeStakerConfig (0.01s)
TestValidatorConfig
Stack Traces | 0.010s run time
=== RUN   TestValidatorConfig
    config_test.go:65: goroutine 13 [running]:
        runtime/debug.Stack()
        	/opt/hostedtoolcache/go/1.25.5/x64/src/runtime/debug/stack.go:26 +0x5e
        github.com/offchainlabs/nitro/util/testhelpers.RequireImpl({0x2ff6330, 0xc000483180}, {0x2fcc880, 0xc000158d38}, {0x0, 0x0, 0x0})
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/util/testhelpers/testhelpers.go:29 +0x55
        github.com/offchainlabs/nitro/cmd/nitro.Require(0xc000483180, {0x2fcc880, 0xc000158d38}, {0x0, 0x0, 0x0})
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/cmd/nitro/config_test.go:277 +0x5d
        github.com/offchainlabs/nitro/cmd/nitro.TestValidatorConfig(0xc000483180)
        	/home/runner/work/nitro-espresso-integration/nitro-espresso-integration/cmd/nitro/config_test.go:65 +0x6d
        testing.tRunner(0xc000483180, 0x2da8998)
        	/opt/hostedtoolcache/go/1.25.5/x64/src/testing/testing.go:1934 +0xea
        created by testing.(*T).Run in goroutine 1
        	/opt/hostedtoolcache/go/1.25.5/x64/src/testing/testing.go:1997 +0x465
        
    config_test.go:65: �[31;1m [] 2 error(s) decoding:
        
        * 'node.batch-poster' has invalid keys: espresso-register-service-config
        * 'node.espresso' has invalid keys: espresso-batch-poster, espresso-caff-node, espresso-streamer �[0;0m
--- FAIL: TestValidatorConfig (0.01s)

📣 Thoughts on this report? Let Codecov know! | Powered by Codecov

@varun-doshi varun-doshi marked this pull request as ready for review January 13, 2026 06:44
@varun-doshi
Copy link
Author

varun-doshi commented Jan 13, 2026

I had to move AddressValidRangeConfig type to the new package or else I was running into circular dependency issue. (espresso batch poster depends on arbnode and arbnode depends on espresso batch poster)

arbnode/node.go Outdated
Comment on lines 61 to 62
EspressoCaffNode EspressoCaffNodeConfig `koanf:"espresso-caff-node"`
EspressoBatchPoster espressobatchposter.EspressoBatchPosterConfig `koanf:"espresso-batch-poster"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make it shorter here. Since the struct name has been EspressoConfig, I think we can omit espresso in its fields.

And both Caff node and poster are using espresso streamer, there are some same configs. I would like to add a Streamer field to config the espresso streamer and both bathcer and caff node read it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HotShotBlock and Dangerous.HotShotBlock have been added to the StreamerConfig. Batcher and Caff node will read values from there.

The retryTime could possibly also be shared but I noticed the Default values for retryTime is different in Batcher vs Caff
In Batcher:
EspressoTxnsPollingInterval: time.Second,

In Caff:
RetryTime: time.Second * 2,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you re-organize them? Otherwise it woule be confusing to use

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to clarify; I'll add EspressoTxnsPollingInterval in the streamerConfig?
If so, what should we keep the default value?
time.Second or time.Second*2

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think time.Second makes more sense here for now. We want to be polling slightly faster than hotshot will produce blocks, which right now can be down to around 1 block every 2 seconds.

There is some more work being done to increase the speed of hotshot, so this might change again in the future, but for now, 1 second should be fine.

arbnode/node.go Outdated
"github.com/offchainlabs/nitro/daprovider/das"
"github.com/offchainlabs/nitro/daprovider/data_streaming"
"github.com/offchainlabs/nitro/daprovider/factory"
espressobatchposter "github.com/offchainlabs/nitro/espresso/batch_poster"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to use a new package? Maybe I am wrong but I think we just need a file under the arbnode folder

Comment on lines -348 to -371

// Espresso Specific config //

// Hotshot currently produces blocks at average of 2 seconds
// We set it to 1 second to get updates more often than blocks are produced
EspressoTxnsPollingInterval: time.Second,
// We should send to espresso at a speed faster than the speed nitro is producing messages
EspressoTxnsSendingInterval: 125 * time.Millisecond,
EspressoTxnsResubmissionInterval: 2 * time.Second,
ResubmitEspressoTxDeadline: 10 * time.Minute,
HotShotUrl: "",
EspressoTeeType: "NITRO",
EspressoRegisterServiceConfig: espressotee.DefaultEspressoRegisterServiceConfig,
// EspressoTxSizeLimit is 1 MB, to have some buffer we set it to 900 KB
EspressoTxSizeLimit: 900 * 1024,
UserDataAttestationFile: "",
QuoteFile: "",
AttestationServiceURL: "",
HotShotBlock: 1,
HotShotFirstPostingBlock: 1,
InitBatcherAddresses: []string{},
EspressoEventPollingStep: 100,
AddressMonitorStep: 100,
AddressMonitorStartL1: 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment on lines 2481 to 2511
func TestEspressoConfigParsing(t *testing.T) {
inputSource := map[string]interface{}{
"sequencer": true,
"espresso": map[string]interface{}{
"espresso-caff-node": map[string]interface{}{
"enable": true,
},
"espresso-batch-poster": map[string]interface{}{
"espresso-tee-type": "NITRO",
"hotshot-url": "http://localhost:8080",
"espresso-tx-size-limit": int64(900000),
"hotshot-block": uint64(100),
},
},
}

k := koanf.New(".")
err := k.Load(confmap.Provider(inputSource, "."), nil)
require.NoError(t, err)

var parsedConfig arbnode.Config
err = k.UnmarshalWithConf("", &parsedConfig, koanf.UnmarshalConf{Tag: "koanf"})
require.NoError(t, err)

assert.Equal(t, true, parsedConfig.Sequencer)
assert.Equal(t, true, parsedConfig.Espresso.EspressoCaffNode.Enable)
assert.Equal(t, "NITRO", parsedConfig.Espresso.EspressoBatchPoster.EspressoTeeType)
assert.Equal(t, "http://localhost:8080", parsedConfig.Espresso.EspressoBatchPoster.HotShotUrl)
assert.Equal(t, int64(900000), parsedConfig.Espresso.EspressoBatchPoster.EspressoTxSizeLimit)
assert.Equal(t, uint64(100), parsedConfig.Espresso.EspressoBatchPoster.HotShotBlock)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common_test.go is an upstream file we should avoid unnecessary changes. Can you move this unit test to another file that starts with espresso? If no appropriate file exists, you can create a new one.

Copy link
Member

@ImJeremyHe ImJeremyHe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks much better! Would you mind writing a script to convert the JSON config? Python would be fine, and we can put it under /scripts.

Comment on lines 144 to 153
espressoConfig EspressoBatchConfigFetcher
bytesType abi.Type
bytes32ArrayType abi.Type
blobsAttestationArguments abi.Arguments
espressoStreamer *espressostreamer.EspressoStreamer
espressoBatcherAddrMonitor BatcherAddrMonitorInterface
espressoRestarting bool
signerAddr common.Address

espressoStreamerConfig EspressoStreamerConfigFetcher
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can pass the Espresso config directly here. I know it feels a bit awkward since the Espresso config contains caff-node that the batcher doesn't need, but this approach keeps the change minimally invasive.

Comment on lines +71 to +74
type EspressoStreamerConfig struct {
HotShotBlock uint64 `koanf:"hotshot-block"`
Dangerous DangerousEspressoStreamerConfig `koanf:"dangerous"`
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not this contain some fields indicating the polling interval?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes here is the reasoning

#916 (comment)

@varun-doshi
Copy link
Author

It looks much better! Would you mind writing a script to convert the JSON config? Python would be fine, and we can put it under /scripts.

do you mean a py script to convert the old json config to the new one?

@ImJeremyHe
Copy link
Member

It looks much better! Would you mind writing a script to convert the JSON config? Python would be fine, and we can put it under /scripts.

do you mean a py script to convert the old json config to the new one?

Yes!. I think Python should be the most appropriate since there is already a py script under this folder.

@varun-doshi
Copy link
Author

Script to migrate old config to new config added: migrate-config-file.py
To run:

python3 ./scripts/migrate-config-file.py config.json new_node_config.json

I've tested the script with the following config file:

{  "parent-chain": {
    "connection": {
      "url": "${parent_chain_url_secret_arn}"
    }
  },
  "chain": {
    "info-files": ["/config/l2_chain_info.json"]
  },
  "node": {
    "parent-chain-reader": {
      "poll-interval": "60s",
      "poll-only": true
    },
    "staker": {
      "parent-chain-wallet": {
        "private-key": "${staker_secret_arn}"
      },
      "disable-challenge": false,
      "enable": true,
      "staker-interval": "120s",
      "make-assertion-interval": "120s",
      "strategy": "MakeNodes"
    },
    "sequencer": true,
    "dangerous": {
      "no-sequencer-coordinator": true,
      "disable-blob-reader": true
    },
    "delayed-sequencer": {
      "enable": true,
      "finalize-distance": 6,
      "use-merge-finality": false
    },
    "seq-coordinator": {
      "enable": false,
      "redis-url": "redis://redis:6379",
      "lockout-duration": "30s",
      "lockout-spare": "1s",
      "my-url": "",
      "retry-interval": "0.5s",
      "seq-num-duration": "24h0m0s",
      "update-interval": "3s"
    },
    "espresso-caff-node": {
      "enable": false,
        "namespace": 23,
        "espresso-tee-type": "${espresso_tee_type}",
        "dangerous":{
            "minimum-hotshot-block-num":56,
            "ignore-database-hotshot-block":true,
            "ignore-database-from-block":false
        }
    },
    "batch-poster": {
      "enable": true,
      "data-poster": {
        "max-base-fee": 10
      },
      "espresso-register-service-config": {
        "max-retries": 5,
        "max-base-fee": 10
      },
      "poll-interval": "10s",
      "redis-url": "",
      "l1-block-bound": "ignore",
      "wait-for-max-delay": true,
      "resubmit-espresso-tx-deadline": "2m",
      "parent-chain-wallet": {
        "private-key": "${batch_poster_secret_arn}"
      },
      "max-delay": "1h0m0s",
      "max-empty-batch-delay": "1h0m0s",
      "post-4844-blobs": false,
      "hotshot-url": "https://localhost:9090",
      "light-client-address": "${light_client_address}",
      "espresso-tee-type": "${espresso_tee_type}",
      "hotshot-first-posting-block": 1,
      "address-monitor-start-l1": 1,
      "hotshot-block": 10,
      "address-valid-ranges":[
        {
            "address":"${address1}",
            "from":1,
            "to":99
        },
        {
            "address":"${address2}",
            "from":99,
            "to":199
        }
      ]
    },
    "block-validator": {
      "enable": true,
      "validation-server": {
        "url": "${validation_server_url}",
        "jwtsecret": "/config/val_jwt.hex"
      }
    },
    "feed": {
      "input": {
        "url": ""
      },
      "output": {
        "enable": true,
        "signed": false
      }
    },
    "data-availability": {
      "enable": false
    }
  },
  "execution": {
    "sequencer": {
      "enable": true
    },
    "forwarding-target": "null"
  },
  "persistent": {
    "chain": "local",
    "db-engine": "leveldb"
  },
  "ws": {
    "addr": "0.0.0.0",
    "api": [
      "eth",
      "net",
      "web3",
      "arb",
      "debug",
      "txpool"
    ]
  },
  "http": {
    "addr": "0.0.0.0",
    "api": [
      "eth",
      "net",
      "web3",
      "arb",
      "debug",
      "txpool"
    ],
    "vhosts": "*",
    "corsdomain": "*"
  }
}

Which gives the following updated config(this is also what is being used in the Go test: TestEspressoConfigMigration):

{
  "chain": {
    "info-files": [
      "/config/l2_chain_info.json"
    ]
  },
  "espresso": {
    "batch-poster": {
      "address-monitor-start-l1": 1,
      "address-valid-ranges": [
        {
          "address": "${address1}",
          "from": 1,
          "to": 99
        },
        {
          "address": "${address2}",
          "from": 99,
          "to": 199
        }
      ],
      "espresso-register-service-config": {
        "max-base-fee": 10,
        "max-retries": 5
      },
      "espresso-tee-type": "${espresso_tee_type}",
      "hotshot-first-posting-block": 1,
      "hotshot-url": "https://localhost:9090",
      "resubmit-espresso-tx-deadline": "2m"
    },
    "caff-node": {
      "dangerous": {
        "ignore-database-from-block": false,
        "ignore-database-hotshot-block": true
      },
      "enable": false,
      "espresso-tee-type": "${espresso_tee_type}",
      "namespace": 23
    },
    "streamer": {
      "dangerous": {
        "minimum-hotshot-block-num": 56
      },
      "hotshot-block": 10
    }
  },
  "execution": {
    "forwarding-target": "null",
    "sequencer": {
      "enable": true
    }
  },
  "http": {
    "addr": "0.0.0.0",
    "api": [
      "eth",
      "net",
      "web3",
      "arb",
      "debug",
      "txpool"
    ],
    "corsdomain": "*",
    "vhosts": "*"
  },
  "node": {
    "batch-poster": {
      "data-poster": {
        "max-base-fee": 10
      },
      "enable": true,
      "l1-block-bound": "ignore",
      "light-client-address": "${light_client_address}",
      "max-delay": "1h0m0s",
      "max-empty-batch-delay": "1h0m0s",
      "parent-chain-wallet": {
        "private-key": "${batch_poster_secret_arn}"
      },
      "poll-interval": "10s",
      "post-4844-blobs": false,
      "redis-url": "",
      "wait-for-max-delay": true
    },
    "block-validator": {
      "enable": true,
      "validation-server": {
        "jwtsecret": "/config/val_jwt.hex",
        "url": "${validation_server_url}"
      }
    },
    "dangerous": {
      "disable-blob-reader": true,
      "no-sequencer-coordinator": true
    },
    "data-availability": {
      "enable": false
    },
    "delayed-sequencer": {
      "enable": true,
      "finalize-distance": 6,
      "use-merge-finality": false
    },
    "feed": {
      "input": {
        "url": ""
      },
      "output": {
        "enable": true,
        "signed": false
      }
    },
    "parent-chain-reader": {
      "poll-interval": "60s",
      "poll-only": true
    },
    "seq-coordinator": {
      "enable": false,
      "lockout-duration": "30s",
      "lockout-spare": "1s",
      "my-url": "",
      "redis-url": "redis://redis:6379",
      "retry-interval": "0.5s",
      "seq-num-duration": "24h0m0s",
      "update-interval": "3s"
    },
    "sequencer": true,
    "staker": {
      "disable-challenge": false,
      "enable": true,
      "make-assertion-interval": "120s",
      "parent-chain-wallet": {
        "private-key": "${staker_secret_arn}"
      },
      "staker-interval": "120s",
      "strategy": "MakeNodes"
    }
  },
  "parent-chain": {
    "connection": {
      "url": "${parent_chain_url_secret_arn}"
    }
  },
  "persistent": {
    "chain": "local",
    "db-engine": "leveldb"
  },
  "ws": {
    "addr": "0.0.0.0",
    "api": [
      "eth",
      "net",
      "web3",
      "arb",
      "debug",
      "txpool"
    ]
  }
}

espressoRestarting bool
signerAddr common.Address

// espressoStreamerConfig EspressoStreamerConfigFetcher

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remove this comment? if not needed

}

func TestEspressoConfigMigration(t *testing.T) {
const migratedJSON = `{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this not be a file? That will be cleaner

@@ -0,0 +1,114 @@
#!/usr/bin/env python3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this script should live in https://github.com/EspressoSystems/integration-internal-tools instead of this repo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this repository is used externally, it would be nice to have it here. The script isn’t sensitive, so I think it’s fine to include it as well.

Comment on lines +11 to +17
"espresso-tee-type": ("batch-poster", "espresso-tee-type"),
"espresso-register-service-config": ("batch-poster", "espresso-register-service-config"),
"hotshot-url": ("batch-poster", "hotshot-url"),
"espresso-txns-polling-interval": ("batch-poster", "espresso-txns-polling-interval"),
"espresso-txns-sending-interval": ("batch-poster", "espresso-txns-sending-interval"),
"espresso-txns-resubmission-interval": ("batch-poster", "espresso-txns-resubmission-interval"),
"resubmit-espresso-tx-deadline": ("batch-poster", "resubmit-espresso-tx-deadline"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also rename the espresso* configs? They’re already under the espresso config block, so the prefix feels a bit redundant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants