Skip to content

Create Live Snapshot without shutting down node #2816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

ganeshvanahalli
Copy link
Contributor

@ganeshvanahalli ganeshvanahalli commented Dec 3, 2024

This PR enables creation of live snapshot of databases (arbitrumdata, l2chaindata, ancient and wasm) without having to shutdown the node.

The feature is disabled by default and can be enabled using the --execution.live-db-snapshotter.enable for execution databases (l2chaindata, ancient and wasm) and --node.live-db-snapshotter.enable for consensus database (arbitrumdata)

Note: This feature is only available for archive nodes running on pebble databases

Sample usage-

start node => ./target/bin/nitro --conf.file=<pathToConfigWithSnapshotDir>
trigger snapshot creation => curl -X POST 127.0.0.1:8547 -H "Content-Type: application/json" --data '{"jsonrpc": "2.0","method": "arbdebug_createDBSnapshot","params": [],"id": 1}'

Testing

Triggered snapshot creation and successfully reused it run a new node in multiple scenario- small and large db sizes for arb1 and arb-sepolia nodes.

ArbSepolia-

  • Snapshot creation was triggered and the node was shutdown shortly after. Logs-
...
INFO [04-14|12:53:18.503] created block                            l2Block=33997 l2BlockHash=e56776..1a140f
INFO [04-14|12:53:19.504] created block                            l2Block=34215 l2BlockHash=e8685d..59a99f
INFO [04-14|12:53:25.457] Live databases snapshot creation scheduled databases="l2chaindata, ancient, wasm"
INFO [04-14|12:53:31.933] InboxTracker                             sequencerBatchCount=624 messageCount=34376 l1Block=4,213,059 l1Timestamp=2023-09-03T03:07:14+0530
INFO [04-14|12:53:31.938] Writing cached state to disk             block=34216 hash=fef38c..4f2aa6 root=53844a..0dd4cb
INFO [04-14|12:53:31.941] Persisted trie from memory database      nodes=923 flushnodes=0 size=200.51KiB flushsize=0.00B time=3.410417ms flushtime=0s gcnodes=1360 gcsize=461.84KiB gctime=1.379076ms livenodes=3288 livesize=1.06MiB
INFO [04-14|12:53:31.941] Writing cached state to disk             block=34215 hash=e8685d..59a99f root=a0fdd3..b1abc7
INFO [04-14|12:53:31.941] Persisted trie from memory database      nodes=32  flushnodes=0 size=9.83KiB   flushsize=0.00B time="129.5µs"  flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=3256 livesize=1.05MiB
INFO [04-14|12:53:31.941] Writing cached state to disk             block=34089 hash=5360e3..b9bc5b root=c22df4..e81091
INFO [04-14|12:53:31.942] Persisted trie from memory database      nodes=202 flushnodes=0 size=60.05KiB  flushsize=0.00B time="664.958µs" flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=3054 livesize=1017.41KiB
INFO [04-14|12:53:31.942] Writing cached state to disk             block=34089 hash=5360e3..b9bc5b root=c22df4..e81091
INFO [04-14|12:53:31.942] Persisted trie from memory database      nodes=0   flushnodes=0 size=0.00B     flushsize=0.00B time="1.125µs"   flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=3054 livesize=1017.41KiB
INFO [04-14|12:53:31.942] Writing snapshot state to disk           root=8426bf..268529
INFO [04-14|12:53:31.942] Persisted trie from memory database      nodes=0   flushnodes=0 size=0.00B     flushsize=0.00B time="1.708µs"   flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=3054 livesize=1017.41KiB
INFO [04-14|12:53:31.942] Beginning snapshot creation              databases="l2chaindata, ancient, wasm"
INFO [04-14|12:53:32.058] Live snapshot was successfully created   databases="l2chaindata, ancient, wasm" timeTaken=115.360042ms
INFO [04-14|12:53:32.058] Live databases snapshot creation scheduled databases=arbitrumdata
INFO [04-14|12:53:32.058] Beginning snapshot creation              databases=arbitrumdata
INFO [04-14|12:53:32.058] created block                            l2Block=34216 l2BlockHash=fef38c..4f2aa6
INFO [04-14|12:53:32.098] Live snapshot was successfully created   databases=arbitrumdata                 timeTaken=40.812166ms
INFO [04-14|12:53:33.059] created block                            l2Block=34375 l2BlockHash=8cc93f..430811
INFO [04-14|12:53:47.867] InboxTracker                             sequencerBatchCount=627 messageCount=34586 l1Block=4,213,233 l1Timestamp=2023-09-03T03:45:47+0530
INFO [04-14|12:53:47.881] created block                            l2Block=34376 l2BlockHash=1e149b..82f6b5
INFO [04-14|12:53:48.882] created block                            l2Block=34585 l2BlockHash=8526e2..c68438
INFO [04-14|12:54:04.103] InboxTracker                             sequencerBatchCount=630 messageCount=34785 l1Block=4,213,407 l1Timestamp=2023-09-03T04:23:53+0530
INFO [04-14|12:54:04.106] created block                            l2Block=34586 l2BlockHash=d3a43e..132a57
INFO [04-14|12:54:05.107] created block                            l2Block=34784 l2BlockHash=2f7049..ca7e8b
INFO [04-14|12:54:22.176] InboxTracker                             sequencerBatchCount=633 messageCount=34984 l1Block=4,213,570 l1Timestamp=2023-09-03T05:02:34+0530
INFO [04-14|12:54:22.179] created block                            l2Block=34785 l2BlockHash=03282f..e9d5df
INFO [04-14|12:54:23.179] created block                            l2Block=34983 l2BlockHash=de0795..b5a991
INFO [04-14|12:54:46.244] InboxTracker                             sequencerBatchCount=638 messageCount=35230 l1Block=4,213,819 l1Timestamp=2023-09-03T06:02:11+0530
INFO [04-14|12:54:46.247] created block                            l2Block=34984 l2BlockHash=b40f6b..c75281
INFO [04-14|12:54:47.251] created block                            l2Block=35229 l2BlockHash=4400b5..8de0ae
^CINFO [04-14|12:55:02.629] shutting down because of sigint
INFO [04-14|12:55:02.629] HTTP server stopped                      endpoint=127.0.0.1:8547
INFO [04-14|12:55:02.629] rpc response                             method=eth_getBlockByHash logId=464 err="Post \"https://eth-sepolia.g.alchemy.com/v2/xQDQsyXsut3ip2OS7D-aGODN4br4LS53\": context canceled" result=null attempt=0 args="[\"0x34bf97f5dce22710360e45dee79bcb57ffefc9e888553a2a719dbd319b219244\", false]"
INFO [04-14|12:55:02.631] Writing cached state to disk             block=35229 hash=4400b5..8de0ae root=f6c3fe..b5a68f
INFO [04-14|12:55:02.637] Persisted trie from memory database      nodes=2736 flushnodes=0 size=507.20KiB flushsize=0.00B time=6.41175ms   flushtime=0s gcnodes=24162 gcsize=7.74MiB   gctime=28.504046ms livenodes=3230 livesize=1.02MiB
INFO [04-14|12:55:02.637] Writing cached state to disk             block=35228 hash=071444..763498 root=e906f2..2c7aa8
INFO [04-14|12:55:02.638] Persisted trie from memory database      nodes=27   flushnodes=0 size=8.39KiB   flushsize=0.00B time="98.792µs"  flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=3203 livesize=1.01MiB
INFO [04-14|12:55:02.638] Writing cached state to disk             block=35102 hash=84460b..92259d root=352c72..228a14
INFO [04-14|12:55:02.639] Persisted trie from memory database      nodes=399  flushnodes=0 size=106.75KiB flushsize=0.00B time=1.032208ms  flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=2804 livesize=931.35KiB
INFO [04-14|12:55:02.639] Writing cached state to disk             block=35102 hash=84460b..92259d root=352c72..228a14
INFO [04-14|12:55:02.639] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time="1.167µs"   flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=2804 livesize=931.35KiB
INFO [04-14|12:55:02.639] Writing snapshot state to disk           root=8426bf..268529
INFO [04-14|12:55:02.639] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time=709ns       flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=2804 livesize=931.35KiB
INFO [04-14|12:55:02.644] Blockchain stopped
  • Node was able to be successfully restarted with databases' snapshots and it resumed block creation from block=34216 without any issues. Logs-
...
INFO [04-14|12:55:47.929] Loaded most recent local block           number=34216 hash=fef38c..4f2aa6 age=1y7mo2w
INFO [04-14|12:55:47.929] Loaded most recent local finalized block number=34215 hash=e8685d..59a99f age=1y7mo2w
INFO [04-14|12:55:47.938] Initialized transaction indexer          range="last 126230400 blocks"
INFO [04-14|12:55:47.939] Setting rebuilding of wasm store to done
INFO [04-14|12:55:47.939] Using pebble as the backing database
INFO [04-14|12:55:47.939] Allocated cache and file handles         database=/Users/ganeshvanahalli/.arbitrum/sepolia-rollup/nitro/arbitrumdata cache=16.00MiB handles=16
WARN [04-14|12:55:49.406] Unclean shutdown detected                booted=2025-04-11T20:38:04+0530 age=2d16h17m
WARN [04-14|12:55:49.406] Unclean shutdown detected                booted=2025-04-14T12:38:41+0530 age=17m8s
WARN [04-14|12:55:49.406] Unclean shutdown detected                booted=2025-04-14T12:53:03+0530 age=2m46s
INFO [04-14|12:55:49.406] Starting peer-to-peer node               instance=nitro/v5614d67-modified/darwin-arm64/go1.23.0
WARN [04-14|12:55:49.407] P2P server will be useless, neither dialing nor listening
INFO [04-14|12:55:49.431] HTTP server started                      endpoint=127.0.0.1:8547 auth=false prefix= cors= vhosts=localhost
INFO [04-14|12:55:49.432] New local node record                    seq=1,744,615,549,430 id=fc91c963eca1a2bf ip=127.0.0.1 udp=0 tcp=0
INFO [04-14|12:55:49.432] Started P2P networking                   self=enode://51f7434288398ed98ae70cc327933dc220ece064025fd555f17feaf7dc90aefe9c5ee8c699e64f252f19068199be8ea2fa85cb54837a25d1a0b6470deb3ab766@127.0.0.1:0
INFO [04-14|12:55:49.433] validation not set up                    err="timeout trying to connect lastError: dial tcp :80: connect: connection refused"
INFO [04-14|12:55:49.437] created block                            l2Block=34217 l2BlockHash=fc37e6..a670a3
INFO [04-14|12:55:50.437] created block                            l2Block=34375 l2BlockHash=8cc93f..430811

Arb1

  • Snapshot creation was triggered and the node was shutdown shortly after. Logs-
...
INFO [04-14|13:29:09.852] created block                            l2Block=3,608,175 l2BlockHash=31ce70..55bbc8
INFO [04-14|13:29:10.853] created block                            l2Block=3,609,437 l2BlockHash=c1fe9c..9706f5
INFO [04-14|13:29:28.426] Live databases snapshot creation scheduled databases="l2chaindata, ancient, wasm"
INFO [04-14|13:29:33.337] InboxTracker                             sequencerBatchCount=5269 messageCount=3615798 l1Block=15,584,964 l1Timestamp=2022-09-22T04:47:22+0530
INFO [04-14|13:29:33.363] Writing cached state to disk             block=3,609,438 hash=c8ce46..bab7f1 root=f73e95..e6fd74
INFO [04-14|13:29:33.369] Persisted trie from memory database      nodes=1204 flushnodes=0 size=297.01KiB flushsize=0.00B time=2.615542ms flushtime=0s gcnodes=2105 gcsize=900.10KiB gctime=3.400622ms livenodes=36 livesize=14.75KiB
INFO [04-14|13:29:33.369] Writing cached state to disk             block=3,609,437 hash=c1fe9c..9706f5 root=f73e95..e6fd74
INFO [04-14|13:29:33.369] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time="1.958µs"  flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=36 livesize=14.75KiB
INFO [04-14|13:29:33.370] Writing cached state to disk             block=3,609,311 hash=4c3bc8..d93b7b root=ee800d..9e286a
INFO [04-14|13:29:33.370] Persisted trie from memory database      nodes=16   flushnodes=0 size=6.85KiB   flushsize=0.00B time="58.208µs" flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=20 livesize=7.90KiB
INFO [04-14|13:29:33.370] Writing cached state to disk             block=3,609,311 hash=4c3bc8..d93b7b root=ee800d..9e286a
INFO [04-14|13:29:33.370] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time=875ns      flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=20 livesize=7.90KiB
INFO [04-14|13:29:33.370] Writing snapshot state to disk           root=c0a69c..101621
INFO [04-14|13:29:33.370] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time="1.209µs"  flushtime=0s gcnodes=0    gcsize=0.00B     gctime=0s         livenodes=20 livesize=7.90KiB
INFO [04-14|13:29:33.370] Beginning snapshot creation              databases="l2chaindata, ancient, wasm"
ERROR[04-14|13:29:33.802] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:29:34.083] Live snapshot was successfully created   databases="l2chaindata, ancient, wasm" timeTaken=713.418167ms
INFO [04-14|13:29:34.083] Live databases snapshot creation scheduled databases=arbitrumdata
INFO [04-14|13:29:34.084] Beginning snapshot creation              databases=arbitrumdata
INFO [04-14|13:29:34.083] created block                            l2Block=3,609,438 l2BlockHash=c8ce46..bab7f1
INFO [04-14|13:29:34.197] Live snapshot was successfully created   databases=arbitrumdata                 timeTaken=113.39525ms
ERROR[04-14|13:29:34.802] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:29:35.084] created block                            l2Block=3,612,117 l2BlockHash=52573e..5527c8
ERROR[04-14|13:29:35.803] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:29:36.084] created block                            l2Block=3,614,797 l2BlockHash=d2ca1d..064e98
INFO [04-14|13:29:37.085] created block                            l2Block=3,615,797 l2BlockHash=07c90a..40f31f
INFO [04-14|13:29:56.765] InboxTracker                             sequencerBatchCount=5276 messageCount=3619962 l1Block=15,585,134 l1Timestamp=2022-09-22T05:22:11+0530
INFO [04-14|13:29:56.785] created block                            l2Block=3,615,798 l2BlockHash=7be3df..94a24c
ERROR[04-14|13:29:56.955] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:29:57.785] created block                            l2Block=3,618,440 l2BlockHash=fead35..3f1e5b
ERROR[04-14|13:29:57.955] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:29:58.786] created block                            l2Block=3,619,961 l2BlockHash=085498..2c2ca8
INFO [04-14|13:30:19.527] InboxTracker                             sequencerBatchCount=5285 messageCount=3625283 l1Block=15,585,339 l1Timestamp=2022-09-22T06:03:31+0530
ERROR[04-14|13:30:19.545] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:30:19.546] created block                            l2Block=3,619,962 l2BlockHash=d2358c..7e910a
ERROR[04-14|13:30:20.545] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:30:20.546] created block                            l2Block=3,622,621 l2BlockHash=6b194b..a98cba
ERROR[04-14|13:30:21.546] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:30:21.546] created block                            l2Block=3,625,268 l2BlockHash=f0749c..695a06
INFO [04-14|13:30:22.546] created block                            l2Block=3,625,282 l2BlockHash=4a3eab..39fd0d
^CINFO [04-14|13:30:42.456] shutting down because of sigint
INFO [04-14|13:30:42.457] HTTP server stopped                      endpoint=127.0.0.1:8547
INFO [04-14|13:30:42.457] rpc response                             method=eth_getBlockByHash logId=444 err="Post \"https://eth-mainnet.g.alchemy.com/v2/Q7U5MNOTIPC0Z3mgYDjzOJjQrbZ1zU22\": context canceled" result=null attempt=0 args="[\"0x24ec8ca6ba791743d77ad5e474b465382172b59057f4bf4c533b8438c388ee00\", false]"
INFO [04-14|13:30:42.463] Writing cached state to disk             block=3,625,282 hash=4a3eab..39fd0d root=c6ef21..93277e
INFO [04-14|13:30:42.475] Persisted trie from memory database      nodes=3744 flushnodes=0 size=736.99KiB flushsize=0.00B time=12.676625ms flushtime=0s gcnodes=10657 gcsize=4.33MiB   gctime=17.172455ms livenodes=56 livesize=21.53KiB
INFO [04-14|13:30:42.475] Writing cached state to disk             block=3,625,281 hash=efa15b..eab403 root=c6ef21..93277e
INFO [04-14|13:30:42.476] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time="4.917µs"   flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=56 livesize=21.53KiB
INFO [04-14|13:30:42.476] Writing cached state to disk             block=3,625,155 hash=69086c..6fa0c8 root=13774e..3f9785
INFO [04-14|13:30:42.476] Persisted trie from memory database      nodes=25   flushnodes=0 size=9.13KiB   flushsize=0.00B time="91.125µs"  flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=31 livesize=12.40KiB
INFO [04-14|13:30:42.476] Writing cached state to disk             block=3,625,155 hash=69086c..6fa0c8 root=13774e..3f9785
INFO [04-14|13:30:42.476] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time="1.291µs"   flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=31 livesize=12.40KiB
INFO [04-14|13:30:42.476] Writing snapshot state to disk           root=c0a69c..101621
INFO [04-14|13:30:42.476] Persisted trie from memory database      nodes=0    flushnodes=0 size=0.00B     flushsize=0.00B time="2.292µs"   flushtime=0s gcnodes=0     gcsize=0.00B     gctime=0s          livenodes=31 livesize=12.40KiB
INFO [04-14|13:30:42.476] Blockchain stopped
  • We now try starting the node with the current databases itself to make sure there was not corruption and there wasn't any as the node resumed block creation successfully from block=3,625,282 without any issues. Logs-
...
INFO [04-14|13:30:46.892] Loaded most recent local block           number=3,625,282 hash=4a3eab..39fd0d age=2y7mo5d
INFO [04-14|13:30:46.896] Loaded most recent local finalized block number=3,625,282 hash=4a3eab..39fd0d age=2y7mo5d
INFO [04-14|13:30:46.908] Initialized transaction indexer          range="last 126230400 blocks"
INFO [04-14|13:30:46.909] Setting rebuilding of wasm store to done
INFO [04-14|13:30:46.909] Using pebble as the backing database
INFO [04-14|13:30:46.909] Allocated cache and file handles         database=/Users/ganeshvanahalli/.arbitrum/arb1/nitro/arbitrumdata cache=16.00MiB handles=16
WARN [04-14|13:30:48.359] Unclean shutdown detected                booted=2025-04-11T14:53:57+0530 age=2d22h36m
WARN [04-14|13:30:48.359] Unclean shutdown detected                booted=2025-04-11T14:54:56+0530 age=2d22h35m
INFO [04-14|13:30:48.359] Starting peer-to-peer node               instance=nitro/v5614d67-modified/darwin-arm64/go1.23.0
WARN [04-14|13:30:48.359] P2P server will be useless, neither dialing nor listening
INFO [04-14|13:30:48.393] HTTP server started                      endpoint=127.0.0.1:8547 auth=false prefix= cors= vhosts=localhost
INFO [04-14|13:30:48.394] New local node record                    seq=1,744,363,605,246 id=eca2e97e02c35f27 ip=127.0.0.1 udp=0 tcp=0
INFO [04-14|13:30:48.394] Started P2P networking                   self=enode://a3f5e028ac3efba4535ee9b7fabdea294fc750448c472cde7446f6304f93b48f4a6bd133fdff11e9eef5e51c72199a27648f5d6c1fd678f6652ae177f571aca7@127.0.0.1:0
INFO [04-14|13:30:48.397] validation not set up                    err="timeout trying to connect lastError: dial tcp :80: connect: connection refused"
INFO [04-14|13:31:00.850] InboxTracker                             sequencerBatchCount=5289 messageCount=3627656 l1Block=15,585,434 l1Timestamp=2022-09-22T06:23:02+0530
INFO [04-14|13:31:00.870] created block                            l2Block=3,625,283 l2BlockHash=75dfb3..a88e51
INFO [04-14|13:31:01.874] created block                            l2Block=3,627,655 l2BlockHash=3e6853..2b3bbd
  • Node was able to be successfully restarted with databases' snapshots and it resumed block creation from block=3,609,438 without any issues. Logs-
...
INFO [04-14|13:31:54.893] Loaded most recent local block           number=3,609,438 hash=c8ce46..bab7f1 age=2y7mo5d
INFO [04-14|13:31:54.896] Loaded most recent local finalized block number=3,609,437 hash=c1fe9c..9706f5 age=2y7mo5d
INFO [04-14|13:31:54.925] Initialized transaction indexer          range="last 126230400 blocks"
INFO [04-14|13:31:54.928] Setting rebuilding of wasm store to done
INFO [04-14|13:31:54.932] Using pebble as the backing database
INFO [04-14|13:31:54.932] Allocated cache and file handles         database=/Users/ganeshvanahalli/.arbitrum/arb1/nitro/arbitrumdata cache=16.00MiB handles=16
WARN [04-14|13:31:56.376] Unclean shutdown detected                booted=2025-04-11T14:53:57+0530 age=2d22h37m
WARN [04-14|13:31:56.376] Unclean shutdown detected                booted=2025-04-11T14:54:56+0530 age=2d22h37m
WARN [04-14|13:31:56.376] Unclean shutdown detected                booted=2025-04-14T13:28:39+0530 age=3m17s
INFO [04-14|13:31:56.376] Starting peer-to-peer node               instance=nitro/v5614d67-modified/darwin-arm64/go1.23.0
WARN [04-14|13:31:56.376] P2P server will be useless, neither dialing nor listening
INFO [04-14|13:31:56.403] HTTP server started                      endpoint=127.0.0.1:8547 auth=false prefix= cors= vhosts=localhost
INFO [04-14|13:31:56.404] New local node record                    seq=1,744,617,716,402 id=eca2e97e02c35f27 ip=127.0.0.1 udp=0 tcp=0
INFO [04-14|13:31:56.404] Started P2P networking                   self=enode://a3f5e028ac3efba4535ee9b7fabdea294fc750448c472cde7446f6304f93b48f4a6bd133fdff11e9eef5e51c72199a27648f5d6c1fd678f6652ae177f571aca7@127.0.0.1:0
INFO [04-14|13:31:56.409] validation not set up                    err="timeout trying to connect lastError: dial tcp :80: connect: connection refused"
INFO [04-14|13:31:56.415] created block                            l2Block=3,609,439 l2BlockHash=267c5c..200dbc
INFO [04-14|13:31:57.415] created block                            l2Block=3,612,171 l2BlockHash=247246..9c2888
ERROR[04-14|13:31:57.545] Error pushing finality data from consensus to execution err="unable to get block by number"
INFO [04-14|13:31:58.415] created block                            l2Block=3,614,850 l2BlockHash=0ee7fe..be996f
INFO [04-14|13:31:59.416] created block                            l2Block=3,615,797 l2BlockHash=07c90a..40f31f

Pulls in geth PR- OffchainLabs/go-ethereum#380
Resolves NIT-2658

@cla-bot cla-bot bot added the s Automatically added by the CLA bot if the creator of a PR is registered as having signed the CLA. label Dec 3, 2024
@@ -641,6 +643,11 @@ func mainImpl() int {
deferFuncs = []func(){func() { currentNode.StopAndWait() }}
}

// Live db snapshot creation is only supported on archive nodes
if nodeConfig.Execution.Caching.Archive {
go liveDBSnapshotter(ctx, chainDb, arbDb, execNode.ExecEngine.CreateBlocksMutex(), func() string { return liveNodeConfig.Get().SnapshotDir })
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that:

  • we should use stopwaiter pattern for the liveDBSnapshotter
  • it might be nice to have a config option to disable (not start) the snapshotter, eg. if we are running sequencer, to be extra safe
  • we should be able to support also full nodes (non archive), I am describing it more in the comment for liveDBSnapshotter

Copy link
Member

Choose a reason for hiding this comment

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

Want a config option to enable the snapshotter, off by default.

continue
}

createBlocksMutex.Lock()
Copy link
Contributor

@magicxyyz magicxyyz Jan 8, 2025

Choose a reason for hiding this comment

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

I think that we could rearrange order of things a bit and patch geth a bit we could also support a non archive node (what I believe would be main use case for the db snapshoting).

  1. Instead of triggering the snapshot here, we could schedule a snapshot after next block is created. We could call e.g. execNode.ScheduleDBSnapshot, so we wouldn't need to have access to createBlockMutex or any internals of ExecutionNode (that probably will be especially important for execution split).
  2. In ExecutionEngine.appendBlock if a snapshot was scheduled, we could trigger the snapshot after s.bc.WriteBlockAndSetHeadWithTime.

To support full nodes (non archive) we need to make sure that the state for the block written with WriteBlockAndSetHeadWithTime is committed to disk. To do that we need to force commit the state. It could be done e.g. with a ForceTriedbCommitHook hook that I added in snap sync draft: https://github.com/OffchainLabs/go-ethereum/pull/280/files#diff-53d5f4b8a536ec2a8c8c92bf70b8268f1d77ad77e9f316e6f68a2bcae5303215

The hook would be set to a function created in gethexec scope and that would have access to ExecutionEngine, something like:

hook := func() bool {
    return execEngine.shouldForceCommitState()
}

func (e *ExecutionEngine) shouldForceCommitState() {
    return e.forceCommitState
}

func (e *ExecutionEngine) ScheduleDBSnapshot() {
    e.dbSnapshotScheduled.Store(true)
}

func (e *ExecutionEngine) appendBlock() error {
...
    snapshotScheduled := e.dbSnapshotScheduled.Load()
    if  snapshotScheduled {
        e.forceCommitState = true
    }
    status, err := s.bc.WriteBlockAndSetHeadWithTime(...)
    if err != nil {
        return err
    }
    ...
    if snapshotScheduled {
         e.forceCommitState = false
         chainDb.CreateDBSnapshot(snapshotDir)
    } 
...
}

That setting of the hook can be done similarly as in SnapHelper PR draft: https://github.com/OffchainLabs/nitro/pull/2122/files#diff-19d6494fe5ff01c95bfdd1e4af6d31d75207d21743af80f57f0cf93848a32e3e

Having written that, I am no longer sure if that's that straightforward as I thought when starting this comment 😓 but should be doable :)

Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to go that way, I can split out simplified ForceTriedbCommitHook from my draft PRs, so it can be merged in earlier and used here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you, this seems like a great idea and will see if it works out for snapshotting a fullnode.
Sorry for the delay!

Copy link
Contributor

@magicxyyz magicxyyz Apr 11, 2025

Choose a reason for hiding this comment

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

As I think of it now, as you're already acquiring block creation mutex, you should be able to commit state similarly to how ExecutionEngine.Maintenance does - it calls newly added go-ethereum/core.BlockChain.FlushTrieDB

FlushTrieDB doesn't commit state root for head block and snapshot root, but for the oldest block in BlockChain.triegc (next one to be garbage collected). The node started from such a snapshot might be able to recover, but that would need a little bit more attention.

What might be best option, is to add new method to core.BlockChain that will persist the same roots as when the blockchain is stopped and "Ensure that the entirety of the state snapshot is journaled to disk" by calling bc.snaps.Journal: https://github.com/OffchainLabs/go-ethereum/blob/e6c8bea35d519098cf7cc9c0d3765aef9ab72cbb/core/blockchain.go#L1202-L1257
Then the live snapshot should have a database state similar to the one after stopping the blockchain, but without actually doing so :)

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes sense to me, Im currently testing your last idea out i.e to mimic what the blockchain during a stop without clearing the underlying data that a stop would do

Copy link
Contributor

@magicxyyz magicxyyz Apr 11, 2025

Choose a reason for hiding this comment

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

imo you don't have to worry about the side effects of committing tries from triedb (hashdb) to disk:

  • the recently written trie nodes will be cached in cleans cache
  • the hashdb referencing/dereferencing mechanism should work just fine with the commits

the main area to be careful and explore seem to be the operations around snapshots e.g. bc.snaps.Journal as the method is meant to be used during shutdown, according to the comment:

// Journal commits an entire diff hierarchy to disk into a single journal entry.
// This is meant to be used during shutdown to persist the snapshot without
// flattening everything down (bad for reorgs).
//
// The method returns the root hash of the base layer that needs to be persisted
// to disk as a trie too to allow continuing any pending generation op.
func (t *Tree) Journal(root common.Hash) (common.Hash, error) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The approach worked for database sizes from thousand blocks to about 3.5 million! Marking the PR as ready for review and for further testing with up-to-date arb1 and arbsepolia databases

Copy link
Contributor

@magicxyyz magicxyyz Apr 16, 2025

Choose a reason for hiding this comment

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

one thing that I think might need looking more into is the unclean shutdown log line e.g. from arb1 started from the snapshot:

WARN [04-14|13:31:56.376] Unclean shutdown detected                booted=2025-04-14T13:28:39+0530 age=3m17s

I think that might be because on normal shutdown latest unclean shutdown marker is removed from db and because we don't stop the node the copy has the unclean shutdown marker. The boot time from the marker seems to match the time when the original node might have been started.

Here's how the shutdown tracker works: https://github.com/OffchainLabs/go-ethereum/blob/master/internal/shutdowncheck/shutdown_tracker.go

  • MarkStartup is called on startup - checks if there are some unclean shutdown markers from previous boots and pushes current boot timestamp to unclean shutdown markers list
  • Stop is called after node is fully stopped - pops latest timestamp from markers

We probably need to somehow rawdb.PopUncleanShutdownMarker from the snapshot.
That might be tricky. The simplest solution would be to pop and then after live snapshot pushing back the marker. That requires a bit of thought what happens if node crashes when doing the live snapshot.

My initial thinking is that if we first save everything needed to disk then pop the marker we should be o.k. unless crashing during pebble Checkpoint corrupts the db. We might need to figure out another way of cleaning up the marker if that's not safe enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems very dicey, as you pointed out a node crashing during checkpointing would definitely want to have this marker so we cant pop it before snapshotting to avoid appearing in our copied database.

@ganeshvanahalli ganeshvanahalli marked this pull request as draft April 9, 2025 11:07
@ganeshvanahalli
Copy link
Contributor Author

  1. Instead of triggering the snapshot here, we could schedule a snapshot after next block is created. We could call e.g. execNode.ScheduleDBSnapshot, so we wouldn't need to have access to createBlockMutex or any internals of ExecutionNode (that probably will be especially important for execution split).
  2. In ExecutionEngine.appendBlock if a snapshot was scheduled, we could trigger the snapshot after s.bc.WriteBlockAndSetHeadWithTime.

I created LiveDBSnapshotter which is a standalone object with stopwaiter pattern that has capability of scheduling snapshotting and creates a snapshot of the database supplied to it on init.

The idea is to have two LiveDBSnapshotters one for execution and one for consensus which works well with the model when execution and consensus are separate and node runners can each trigger snapshot generation for their respective node type i.e arbDB for consensus and l2chaindata, wasm and ancient for execution.

Currently to allow for correct generation of snapshots i.e first chainDB and then arbDB, we've introduced chainedTrigger channel inside LiveDBSnapshotter which enables execution to invoke snapshot generation for consensus, but this can be removed upon execution and consensus split.

@ganeshvanahalli ganeshvanahalli marked this pull request as ready for review April 14, 2025 08:18
Copy link
Contributor

@diegoximenes diegoximenes left a comment

Choose a reason for hiding this comment

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

Nice!
This feature is quite useful 😄

I didn't review the code throughly yet since I think we should redesign its architecture a little bit, by moving snapshot triggering API from Execution to Consensus.
Added a comment questioning that, explaining a bit of the reasoning.
We can discuss that in the team's standup or through a Slack thread too :)

// TODO: after the consensus-execution split, remove this code block and modify LiveDBSnapshotter to
// directly read from sigur2. Currently execution will trigger snapshot creation of consensus side once
// its own snapshotting is complete.
var executionDBSnapShotTrigger, consensusDBSnapShotTrigger chan struct{}
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick:

Suggested change
var executionDBSnapShotTrigger, consensusDBSnapShotTrigger chan struct{}
var executionDBSnapshotTrigger, consensusDBSnapshotTrigger chan struct{}

@@ -531,6 +557,8 @@ func mainImpl() int {
l1Client,
func() *gethexec.Config { return &liveNodeConfig.Get().Execution },
liveNodeConfig.Get().Node.TransactionStreamer.SyncTillBlock,
executionDBSnapShotTrigger,
consensusDBSnapShotTrigger, // execution will invoke conensus's db snapshotting
Copy link
Contributor

@diegoximenes diegoximenes Apr 15, 2025

Choose a reason for hiding this comment

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

Today we have a circular call dependency between Consensus and Execution, but ideally we would like to get rid of that, and we are moving in that direction.

In the current model that we have Consensus calls Execution, e.g., Consensus retrieves messages from different sources and then calls Execution to generate blocks related to those messages.
We want to avoid the need of Execution calling Consensus.
This can also enable us to, in the future, have a single Consensus node controlling multiple Execution nodes.

That said, I think we should move the snapshot trigger API from Execution to Consensus, and Consensus will trigger Execution's DB snapshot.
How about that?
We already have some RPC APIs on Consensus, so we can reuse the API framework that is implemented, examples are the BlockValidatorAPI and the MaintenanceAPI.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, in the near future we intend to split Consensus and Execution in different processes.
So Execution communicating with Consensus directly through a channel can make the splitting a little bit less straightforward.

Today we rely on having functions on ConsensusSequencer interface in which Execution can use to call Consensus.
If we decide to keep the this snapshot trigger API on Execution we could use this interface to enable Execution -> Consensus communication here too.

Copy link
Contributor Author

@ganeshvanahalli ganeshvanahalli Apr 18, 2025

Choose a reason for hiding this comment

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

Thanks for the context! But for the working snapshot to be generated we need to first snapshot execution's dbs and then consensus's db i.e blockheight in chainDB <= arbDB.

I understand your suggestion that the execution shouldn't call consensus actions, but this is merely a signal transfer to start db snapshotting. Moreover it is implemented in a way to be easily removed after the split with the idea that both consensus and execution will each have snaphotting API that will snapshot their respective dbs.

Trying to make consensus trigger execution's database creation is essentially reverse of what we currently have but moving code around to consensus side, the code that will anyway be need to be removed after the split.

If this is really a priority then I can completely decouple it so that livedbsnapshotter will return a success signal after db creation and nitro.go can handle signals entirely, let me know.

Copy link
Contributor

Choose a reason for hiding this comment

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

I will ping you so we can chat 🙂, it will be easier to give you more context, but here goes a summary.

But for the working snapshot to be generated we need to first snapshot execution's dbs and then consensus's db i.e blockheight in chainDB <= arbDB

There is a common misconception, that the procedure that coordinates an action between multiple components should be placed where the action starts to happen.
The analogy in computer networking, and distributed systems, is that the control plane doesn't need to live where the data plane is.

Nitro, in some parts, follows this colocation pattern today, which has some issues.
One example is that the procedure that triggers regular sequencing is placed where the sequencing takes place, Execution.
This PR #3111 moves away from that pattern, and moves sequencing triggering to Consensus, there are several benefits with that.
So, with this PR, sequencing triggering coordination will not be placed where sequencing takes place anymore.

That said, live snapshot coordination can be placed in Consensus, and we will still be able to snapshot Execution's DB before Consensus' DB.
We could even place it in another component, like a CLI similar to dbconv.

Moreover it is implemented in a way to be easily removed after the split with the idea that both consensus and execution will each have snaphotting API that will snapshot their respective dbs.
Trying to make consensus trigger execution's database creation is essentially reverse of what we currently have but moving code around to consensus side, the code that will anyway be need to be removed after the split.

I don't fully understand that 😕

Why code related to live snapshot will be removed after the split?

If I inferred correctly, you are saying that, only after the split, we will have an API in Consensus that will trigger a snapshot of Consensus's DB, so a human node operator will be able to first trigger Execution's DB snapshot, and after that trigger Consensus' DB snapshot, right?

I am more inclined to have an UX in which the human node operator only triggers snapshot once, and the software is responsible to coordinate snapshotting Execution and Consensus sides in the correct order.
It is simpler for human operators and less error prone too 🙂
Also, we should move forward in a way that we will not need to redo a big chunk of this feature in order to make Consensus/Execution split ready, which will happen soon 🙂 , we want this feature to be fully compliant with Consensus and Execution split from the start.

Also, we intend to have other Execution clients, such as one based on reth, but we do not intend to have multiple Consensus clients, at least in the near future.
If we keep live snapshot coordination in Execution side then we are unnecessarily increasing the effort to develop new Execution clients, which will need to implement that coordination behavior.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, to snapshot Consensus' DB we will need to hold insertionMutex, right?

@ganeshvanahalli
Copy link
Contributor Author

After discussing with @diegoximenes regarding implementation specifics to be in sync with execution split- I think this PR needs to wait until the communication between consensus and execution is more concrete, but can make it more in line to the consensus-execution logic now as well if this PR is a priority.

For context- we first need to snapshot chainDB (execution) before arbDB (consensus) but we want the consensus to trigger this, which means consensus should invoke a call to execution to start the snapshot process or to mark it as scheduled- but obviously we cannot keep the communication open and wait till the snapshot is generated to return a result to consensus so we would need another function that consensus can ping to, to get the status of snapshot creation.

What do you think? @joshuacolvin0 @diegoximenes

@diegoximenes
Copy link
Contributor

diegoximenes commented Apr 30, 2025

After discussing with @diegoximenes regarding implementation specifics to be in sync with execution split- I think this PR needs to wait until the communication between consensus and execution is more concrete, but can make it more in line to the consensus-execution logic now as well if this PR is a priority.

For context- we first need to snapshot chainDB (execution) before arbDB (consensus) but we want the consensus to trigger this, which means consensus should invoke a call to execution to start the snapshot process or to mark it as scheduled- but obviously we cannot keep the communication open and wait till the snapshot is generated to return a result to consensus so we would need another function that consensus can ping to, to get the status of snapshot creation.

What do you think? @joshuacolvin0 @diegoximenes

It is OK to wait until this PR #3111 is merged before you continue working on live snapshots 🙂
It will be a bit easier to handle locks related to snapshotting, Execution's createBlocksMutex, and Consensus' insertionMutex, after this PR is merged.

Considering that snapshotting can take a while, you can implement something like this:

  • Add an RPC SnapshotAPI to Consensus, similar to what is done with MaintenanceAPI here
    Service: &MaintenanceAPI{
    .
  • Add TriggerSnapshot and SnapshotStatus functions to ExecutionClient interface here
    type ExecutionClient interface {
  • Consensus's SnaptshotAPI.Trigger will:
  1. Lock TransactionStreamer's insertionMutex
  2. Call ExecutionClient.TriggerSnapshot.
  3. Periodically call ExecutionClient.SnapshotStatus to identify when Execution's snapshot ended.
  4. After Execution snapshot completed it will snapshot Consensus's DB
  5. Unlock insertionMutex
  • ExecutionClient.TriggerSnapshot will lock ExecutionEngine's createBlocksMutex, and then snapshot asynchronously, and unlock createBlocksMutex after snapshot completes.

Also, we could only lock insertionMutex after Execution's snapshot completes, to allow Consensus's DB to move forward while Execution snapshotting is happening.
This will require some extra changes though, to avoid Consensus calling Execution to digest a message while Execution is snapshotting for instance, since this call can hang until Execution's createBlocksMutex is released.
I don't see this as a requirement for the first version, but we could do it.

Makes sense to you?

How long it takes to generate a snapshot for Arb1?

@ganeshvanahalli
Copy link
Contributor Author

ganeshvanahalli commented May 1, 2025

Also, we could only lock insertionMutex after Execution's snapshot completes, to allow Consensus's DB to move forward while Execution snapshotting is happening. This will require some extra changes though, to avoid Consensus calling Execution to digest a message while Execution is snapshotting, since this call can hang until Execution's createBlocksMutex is released. I don't see this as a requirement for the first version, but we could do it.

Makes sense to you?

yeah I agree with this

How long it takes to generate a snapshot for Arb1?

not sure, it took 5hr for sepolia (as tested by sre), so arb1 would be way longer than that

Marking the PR as draft

@ganeshvanahalli ganeshvanahalli marked this pull request as draft May 1, 2025 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
s Automatically added by the CLA bot if the creator of a PR is registered as having signed the CLA.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants