diff --git a/.gitignore b/.gitignore
index 4ce3ebf35d58..621132f18ff3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,3 +65,5 @@ debug_container.log
*.synctex.gz
/x/genutil/config/priv_validator_key.json
/x/genutil/data/priv_validator_state.json
+/.envrc
+/.env
diff --git a/.python-version b/.python-version
new file mode 100644
index 000000000000..24ee5b1be996
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.13
diff --git a/Makefile b/Makefile
index 1fcd9a12cab2..dbb4f3c12d1c 100644
--- a/Makefile
+++ b/Makefile
@@ -492,13 +492,17 @@ localnet-debug: localnet-stop localnet-build-dlv localnet-build-nodes
.PHONY: localnet-start localnet-stop localnet-debug localnet-build-env localnet-build-dlv localnet-build-nodes
-test-system: build-v53 build
+build-system-test-current: build
+ mkdir -p ./tests/systemtests/binaries/
+ cp $(BUILDDIR)/simd ./tests/systemtests/binaries/
+
+test-system: build-v53 build-system-test-current
mkdir -p ./tests/systemtests/binaries/
cp $(BUILDDIR)/simd ./tests/systemtests/binaries/
mkdir -p ./tests/systemtests/binaries/v0.53
mv $(BUILDDIR)/simdv53 ./tests/systemtests/binaries/v0.53/simd
$(MAKE) -C tests/systemtests test
-.PHONY: test-system
+.PHONY: test-system build-system-test-current
# build-v53 checks out the v0.53.x branch, builds the binary, and renames it to simdv53.
build-v53:
diff --git a/analysis/__init__.py b/analysis/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/analysis/analysis.ipynb b/analysis/analysis.ipynb
new file mode 100644
index 000000000000..d5e03cd12506
--- /dev/null
+++ b/analysis/analysis.ipynb
@@ -0,0 +1,1934 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "id": "initial_id",
+ "metadata": {
+ "collapsed": true,
+ "ExecuteTime": {
+ "end_time": "2025-11-05T21:35:56.923025Z",
+ "start_time": "2025-11-05T21:35:56.920193Z"
+ }
+ },
+ "source": [
+ "from analysis.read_otel import load_otel_runs\n",
+ "from analysis.analysis import block_summary, plot_block_durations\n",
+ "import pandas as pd"
+ ],
+ "outputs": [],
+ "execution_count": 6
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-11-05T21:35:58.646050Z",
+ "start_time": "2025-11-05T21:35:56.937299Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "runs = load_otel_runs(\"/Users/arc/iavl-bench-data/sims2\")",
+ "id": "1d7a28bf36057a9d",
+ "outputs": [],
+ "execution_count": 7
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-11-05T21:36:03.528568Z",
+ "start_time": "2025-11-05T21:35:58.656243Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "summary_data = []\n",
+ "for name, run in runs.items():\n",
+ " summary = block_summary(run)\n",
+ " summary_data.append({\n",
+ " 'run': name,\n",
+ " 'duration': summary.total_duration_seconds,\n",
+ " 'blocks': summary.block_count,\n",
+ " 'avg_block_time': summary.total_duration_seconds / summary.block_count,\n",
+ " })\n",
+ "pd.DataFrame(summary_data).set_index('run')\n"
+ ],
+ "id": "7eee7d20b6a3ddfc",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " duration blocks avg_block_time\n",
+ "run \n",
+ "iavl1-2 1694.763556 1000 1.694764\n",
+ "iavl1 597.953485 482 1.240567\n",
+ "iavlx 596.864307 718 0.831287\n",
+ "iavlx-2 1208.231131 1000 1.208231"
+ ],
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " duration | \n",
+ " blocks | \n",
+ " avg_block_time | \n",
+ "
\n",
+ " \n",
+ " | run | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | iavl1-2 | \n",
+ " 1694.763556 | \n",
+ " 1000 | \n",
+ " 1.694764 | \n",
+ "
\n",
+ " \n",
+ " | iavl1 | \n",
+ " 597.953485 | \n",
+ " 482 | \n",
+ " 1.240567 | \n",
+ "
\n",
+ " \n",
+ " | iavlx | \n",
+ " 596.864307 | \n",
+ " 718 | \n",
+ " 0.831287 | \n",
+ "
\n",
+ " \n",
+ " | iavlx-2 | \n",
+ " 1208.231131 | \n",
+ " 1000 | \n",
+ " 1.208231 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 8
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-11-05T21:36:07.257996Z",
+ "start_time": "2025-11-05T21:36:03.574598Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "plot_block_durations(runs)",
+ "id": "f8f479875319ace8",
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "data": [
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavl1-2",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "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"
+ },
+ "type": "scatter"
+ },
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavl1",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAQ=="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "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"
+ },
+ "type": "scatter"
+ },
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavlx",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgI="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "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"
+ },
+ "type": "scatter"
+ },
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavlx-2",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "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"
+ },
+ "type": "scatter"
+ }
+ ],
+ "layout": {
+ "template": {
+ "data": {
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "white",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "white",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "#C8D4E3",
+ "linecolor": "#C8D4E3",
+ "minorgridcolor": "#C8D4E3",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "#C8D4E3",
+ "linecolor": "#C8D4E3",
+ "minorgridcolor": "#C8D4E3",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "white",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "#C8D4E3"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "white",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": ""
+ },
+ "bgcolor": "white",
+ "radialaxis": {
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "white",
+ "gridcolor": "#DFE8F3",
+ "gridwidth": 2,
+ "linecolor": "#EBF0F8",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "#EBF0F8"
+ },
+ "yaxis": {
+ "backgroundcolor": "white",
+ "gridcolor": "#DFE8F3",
+ "gridwidth": 2,
+ "linecolor": "#EBF0F8",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "#EBF0F8"
+ },
+ "zaxis": {
+ "backgroundcolor": "white",
+ "gridcolor": "#DFE8F3",
+ "gridwidth": 2,
+ "linecolor": "#EBF0F8",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "#EBF0F8"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "#DFE8F3",
+ "linecolor": "#A2B1C6",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "#DFE8F3",
+ "linecolor": "#A2B1C6",
+ "ticks": ""
+ },
+ "bgcolor": "white",
+ "caxis": {
+ "gridcolor": "#DFE8F3",
+ "linecolor": "#A2B1C6",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "#EBF0F8",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "#EBF0F8",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "text": "Block Duration Comparison"
+ },
+ "xaxis": {
+ "title": {
+ "text": "Block Number"
+ }
+ },
+ "yaxis": {
+ "title": {
+ "text": "Duration (ms)"
+ }
+ },
+ "hovermode": "x unified"
+ },
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "execution_count": 9
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-11-05T21:36:10.802903Z",
+ "start_time": "2025-11-05T21:36:07.300201Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "plot_block_durations(runs, span_name='Commit')",
+ "id": "88882244feb963c9",
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "data": [
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavl1-2",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "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"
+ },
+ "type": "scatter"
+ },
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavl1",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAQ=="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "/Knx0k3SUUAaL90kBrFNQHnpJjEI7ElAAAAAAAAgTEAfhetRuDZZQGIQWDm0YFBAwvUoXI9CVkBg5dAi29lGQAisHFpkl2FA9ihcj8KFUkDHSzeJQfhRQCGwcmiRfVFAGi/dJAZhNUAj2/l+atxXQH0/NV660VZAbxKDwMoRX0AX2c73U1tYQPP91HjpXlZAy6FFtvOdT0CHFtnO9+NMQHoUrkfhAlFAc2iR7XwPT0AfhetRuJ42QPp+arx0i1dAxSCwcmhhVUAlBoGVQyNYQOxRuB6FK1lA8dJNYhAYIkApXI/C9cgyQKjGSzeJoTJAKVyPwvUoMEBQjZduEoMpQOtRuB6FqzJAg8DKoUW2KUAj2/l+avwqQFg5tMh2vitA5KWbxCDwI0CWQ4ts5zsrQKabxCCwcjBA+n5qvHSTMkBg5dAi2zkwQML1KFyPAitApHA9CtcjMEDo+6nx0s0pQFpkO99PzSZAZmZmZmZmHkD+1HjpJlExQO58PzVeOh5AEVg5tMh2OUCNl24Sg4AlQP7UeOkmsRxAokW28/3UGECcxCCwcugVQLOd76fGyxdAiUFg5dCiFkC+nxov3SQYQFpkO99PDRhABoGVQ4tsGECVQ4ts53sYQFTjpZvEoBhA91PjpZvEGEDb+X5qvPQcQHNoke18PxdAHVpkO9/PGUDLoUW28/0bQNEi2/l+ah9AiUFg5dAiH0BzaJHtfD8eQCLb+X5qnDVA001iEFi5HEDtfD81XroaQFpkO99PDRlADAIrhxZZGUBDi2zn+6kZQCcxCKwcWhhAxks3iUFgGEDJdr6fGq8WQKabxCCwchdAQ4ts5/upGEBqvHSTGIQYQLpJDAIrhxdASgwCK4eWF0BfukkMAks7QPp+arx00yNA2/l+ary0IEDdJAaBlcMeQLKd76fGyx1A8KfGSzcJIECJQWDl0CIfQMHKoUW2cxtAyXa+nxovH0A5tMh2vp8bQNEi2/l+6hxAYOXQItv5GkC28/3UeOkaQHNoke18Px5A0SLb+X7qGkBvEoPAyiEbQFTjpZvEIBtALbKd76fGG0Dl0CLb+X4aQPp+arx0kxxAmZmZmZkZH0Bg5dAi23kdQMzMzMzM9FtAxks3iUFYXEBvEoPAyrFcQO18PzVeFm5AbhKDwMrpXUAzMzMzM4tbQJZDi2znq0ZAnMQgsHIoQUDZzvdT4+VYQPyp8dJNQklAsp3vp8ZLakBvEoPAykE/QF66SQwCU1lAAAAAAACQWEDVeOkmMQBXQDEIrBxavFRAJjEIrBwKWEAzMzMzMxNkQKwcWmQ7R2tAnMQgsHLoXUCLbOf7qRlWQKwcWmQ7I2BALbKd76dGPUDJdr6fGo9MQM3MzMzMbFdAi2zn+6nhX0Bg5dAi22lJQKRwPQrXk1FAFK5H4XoUZkBU46WbxNBVQBFYObTI9iFA1XjpJjGIK0A9CtejcL0pQCcxCKwcWiVAJzEIrBzaI0CLbOf7qbErQIlBYOXQYidA2c73U+PlLEA830+Nly4qQEJg5dAiWypAtch2vp86TUAj2/l+auhkQARWDi2y3TtAXrpJDAKbZEBFtvP91DhiQAaBlUOLfGBAvHSTGAQqYkBqvHSTGJxrQBBYObTINkBAu0kMAivHWEDVeOkmMehlQNEi2/l+6l1AexSuR+H6XkDZzvdT40VbQC/dJAaBnV5AQDVeukk8WUA/NV66SdxjQLXIdr6fslxAlUOLbOdDYUCiRbbz/fBhQEFg5dAimyNAQDVeuklMIUBeukkMAusgQAIrhxbZjiRADi2yne+nLkBcj8L1KJwkQNEi2/l+qi5ArkfhehSuL0BeukkMAisyQF66SQwCKyxATDeJQWAlI0CXbhKDwNpCQKabxCCwUjtAaJHtfD+1J0DTTWIQWLkqQBxaZDvfjypAPgrXo3B9KECUGARWDm0mQA8tsp3vpyVAarx0kxiEJ0Coxks3icEkQCUGgZVDiyNAKVyPwvVoKUDXo3A9ClcdQEa28/3U+BxABFYOLbKdH0DC9Shcj0IeQD81XrpJTCBAO99PjZeuIUB9PzVeuskeQKjGSzeJAUFAMgisHFqkMUDfT42XblIpQObQItv5viVA/Knx0k0iJUDO91PjpZshQOSlm8Qg8CBAL90kBoEVIkBBYOXQIpsgQDvfT42XLiFAeekmMQisIUBANV66SQwhQMdLN4lBoCNAH4XrUbieIEBnZmZmZqYgQPLSTWIQGCFAvHSTGASWIECuR+F6FG4iQLbz/dR46SBATmIQWDk0IUDn+6nx0g0hQEOLbOf7qSFARIts5/upIEDwp8ZLN4kiQOxRuB6FayBA3SQGgZVDIkDEILByaFEgQHsUrkfhOiFAmpmZmZnZIEBpke18P3UhQOtRuB6FayJAhetRuB7FIkA5tMh2vt8iQPYoXI/CdSFA1XjpJjHIIEDXo3A9CpcgQCPb+X5qfCBAjZduEoOAIUBYObTIdj4gQOF6FK5HoSBAzvdT46VbIUDByqFFtnMgQIXrUbgehSJAO99PjZduIUD6fmq8dNMhQP7UeOkmsSBALbKd76eGIECgGi/dJMYhQB1aZDvfjyBADi2yne+nN0CHFtnO9zMwQDMzMzMzEzVAUrgehevRKUCe76fGS/cmQDEIrBxapCRA+n5qvHSTI0DD9ShcjwIlQKrx0k1ikCJAL90kBoFVI0BBYOXQIhskQC/dJAaBFSRAQ4ts5/upJECsHFpkO18jQJ3vp8ZLdyVANV66SQxCJUCMbOf7qbElQIGVQ4tsZyNAXrpJDAIrJUBH4XoUrkcjQBWuR+F6FCVAwcqhRbazJEDTTWIQWDklQPT91HjpJiRAi2zn+6mxIkAehetRuN4jQJhuEoPASiRA+n5qvHSTIkAgsHJoke0kQIPAyqFFtiNAuR6F61H4IkBQjZduEkMkQIcW2c730yNA9Shcj8I1JEBwPQrXo/AjQIts5/upsSNAVg4tsp3vI0AIrBxaZHsjQIGVQ4tsZyVATDeJQWBlIkCVQ4ts53slQAisHFpkeyNAqvHSTWLQIkBzaJHtfH8jQFYOLbKdryNAR+F6FK5HI0ArhxbZzvchQF66SQwCKyBA4XoUrkfhIECMbOf7qfEhQKWbxCCwsiFAbef7qfESIECoxks3iUEgQH0/NV66yR5AUI2XbhJDIEDo+6nx0k0eQI/C9ShcjyBAAyuHFtnOH0AkBoGVQwsfQNv5fmq89B5AL90kBoFVIEArhxbZznceQDQzMzMzsx5A/Knx0k3iHkCiRbbz/RQgQJ3vp8ZLtxxAoUW28/3UHkCe76fGSzcfQH0/NV66SR1AtMh2vp8aIEBYObTIdj4gQC/dJAaBFR5ARIts5/spH0B7FK5H4TogQLByaJHt/BxArkfhehSuHEClm8QgsHIfQCcxCKwc2h1Atch2vp8aHUCzne+nxksgQOj7qfHSzR1A/tR46SaxHUCoxks3iQEgQOkmMQisnB1A5dAi2/n+HUDP91PjpZsyQA4tsp3vJyhAKVyPwvXoJkA9CtejcL0lQBkEVg4tsiNANV66SQwCI0AEVg4tst0hQKwcWmQ7nyNAaJHtfD/1JkD+1HjpJrEjQF66SQwCKyVA46WbxCAwJkCmm8QgsPInQEJg5dAi2yZAjGzn+6lxJUAbL90kBoEiQL+fGi/d5CJAWDm0yHY+JUAv3SQGgdUjQFYOLbKdryNA61G4HoVrIkAlBoGVQ8shQDVeukkMAiNAUI2XbhJDI0ATg8DKocUiQAAAAAAAwClAmG4Sg8CKJEB1kxgEVg4nQPHSTWIQ2CVAlUOLbOf7JECBlUOLbKclQH0/NV66ySRAEVg5tMg2IkD7qfHSTQI/QD81XrpJrDNAObTIdr7fLkDn+6nx0o0lQOj7qfHSzSVAK4cW2c53JEByaJHtfP8kQJzEILByKCdAa7x0kxiEJUA9CtejcD0jQFpkO99PDSRA4XoUrkfhKEBKDAIrh9YlQBfZzvdT4yVAd76fGi/dJUC6SQwCK8clQDiJQWDlECVAEFg5tMg2JEASg8DKoUUkQN0kBoGVozFA76fGSzcZXECUGARWDi1hQFCNl24Sk1JA5/up8dJtNED6fmq8dDNhQJMYBFYOnWBAtMh2vp+6ZEBSuB6F63FMQMHKoUW2B2lAKVyPwvVEakCq8dJNYrAyQMuhRbbz5WBAGARWDi1WY0CF61G4Ht1XQCPb+X5qjEtAZmZmZmZqYEAQWDm0yLZoQObQItv5MmNAokW28/2MZUArhxbZzlNjQKFFtvP9HGNA30+Nl25SQUD0/dR46UY0QDQzMzMz219AkxgEVg69XkDRItv5fuZiQL6fGi/dhGhAXrpJDAKDUECsHFpkOz9PQCuHFtnOt2BAukkMAisXXUDJdr6fGj9tQL10kxgELmVAf2q8dJPYOkBaZDvfTyVeQGIQWDm0YGNA1XjpJjFoQUDIdr6fGs9fQIXrUbgehU9AzczMzMwAaEBPjZduEsNjQMuhRbbzoW1AmG4Sg8BiV0A0MzMzM2tWQIlBYOXQqm1AxSCwcmhdaEDXo3A9CudnQEa28/3UZGJAy6FFtvNVWECEwMqhRfJuQJMYBFYOhVxAd76fGi/lXEDTTWIQWJFpQM3MzMzMLEdAzczMzMz8RUB9PzVeuukyQA4tsp3vZ0JAgZVDi2wnNUDIdr6fGm82QJqZmZmZWSxAyHa+nxpvMkAMAiuHFpktQO18PzVemjdADi2yne8nN0BH4XoUrkcrQOOlm8QgUDpAyHa+nxp/QkBSuB6F67E7QH9qvHST+DlAGi/dJAYhNUDLoUW2830sQF66SQwCazZAObTIdr6fLkBWDi2yne8yQPLSTWIQmCpADi2yne9nJkDdJAaBlYMlQF+6SQwCayNA8tJNYhDYJEBoke18P3UmQA4tsp3vZydA5tAi2/m+JEDVeOkmMYglQPp+arx00yFAItv5fmq8IUC0yHa+nxojQLByaJHtPCJAyHa+nxpvIkBMN4lBYGUiQDq0yHa+nyFAa7x0kxiEIkA1XrpJDEIjQH9qvHSTGCNAukkMAisHI0CcxCCwcqgiQGiR7Xw/9SJA6SYxCKxcIUCq8dJNYgBZQH9qvHSTKEJArBxaZDtbY0DrUbgehSdkQA=="
+ },
+ "type": "scatter"
+ },
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavlx",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgI="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "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"
+ },
+ "type": "scatter"
+ },
+ {
+ "line": {
+ "width": 2
+ },
+ "mode": "lines",
+ "name": "iavlx-2",
+ "x": {
+ "dtype": "i2",
+ "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM="
+ },
+ "y": {
+ "dtype": "f8",
+ "bdata": "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"
+ },
+ "type": "scatter"
+ }
+ ],
+ "layout": {
+ "template": {
+ "data": {
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "white",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "white",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "#C8D4E3",
+ "linecolor": "#C8D4E3",
+ "minorgridcolor": "#C8D4E3",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "#C8D4E3",
+ "linecolor": "#C8D4E3",
+ "minorgridcolor": "#C8D4E3",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "white",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "#C8D4E3"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "white",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": ""
+ },
+ "bgcolor": "white",
+ "radialaxis": {
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "white",
+ "gridcolor": "#DFE8F3",
+ "gridwidth": 2,
+ "linecolor": "#EBF0F8",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "#EBF0F8"
+ },
+ "yaxis": {
+ "backgroundcolor": "white",
+ "gridcolor": "#DFE8F3",
+ "gridwidth": 2,
+ "linecolor": "#EBF0F8",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "#EBF0F8"
+ },
+ "zaxis": {
+ "backgroundcolor": "white",
+ "gridcolor": "#DFE8F3",
+ "gridwidth": 2,
+ "linecolor": "#EBF0F8",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "#EBF0F8"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "#DFE8F3",
+ "linecolor": "#A2B1C6",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "#DFE8F3",
+ "linecolor": "#A2B1C6",
+ "ticks": ""
+ },
+ "bgcolor": "white",
+ "caxis": {
+ "gridcolor": "#DFE8F3",
+ "linecolor": "#A2B1C6",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "#EBF0F8",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "#EBF0F8",
+ "linecolor": "#EBF0F8",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "#EBF0F8",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "text": "Commit Duration Comparison"
+ },
+ "xaxis": {
+ "title": {
+ "text": "Block Number"
+ }
+ },
+ "yaxis": {
+ "title": {
+ "text": "Duration (ms)"
+ }
+ },
+ "hovermode": "x unified"
+ },
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "execution_count": 10
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-11-05T21:36:10.820435Z",
+ "start_time": "2025-11-05T21:36:10.818144Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "",
+ "id": "8922bfaae8e7ed45",
+ "outputs": [],
+ "execution_count": null
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/analysis/analysis.py b/analysis/analysis.py
new file mode 100644
index 000000000000..9d8bf55da472
--- /dev/null
+++ b/analysis/analysis.py
@@ -0,0 +1,124 @@
+"""Analysis functions for OpenTelemetry trace and metrics data."""
+
+import duckdb
+from datetime import datetime
+from pydantic import BaseModel
+import pandas as pd
+import plotly.graph_objects as go
+from typing import Dict
+
+# Type alias for runs dictionary (run_name -> DuckDB connection)
+Runs = Dict[str, duckdb.DuckDBPyConnection]
+
+
+class BlockSummary(BaseModel):
+ """Summary of block execution."""
+ start_time: datetime
+ end_time: datetime
+ total_duration_seconds: float
+ block_count: int
+
+
+def block_summary(con: duckdb.DuckDBPyConnection) -> BlockSummary:
+ """
+ Generate a high-level summary of block execution.
+
+ Args:
+ con: DuckDB connection with spans view loaded
+
+ Returns:
+ BlockSummary object with:
+ - start_time: Start time of first block
+ - end_time: End time of last block
+ - total_duration_seconds: Total duration from first block start to last block end
+ - block_count: Total number of blocks processed
+
+ Example:
+ >>> from analysis.read_otel import load_otel_data
+ >>> con = load_otel_data('/path/to/data')
+ >>> summary = block_summary(con)
+ >>> print(f"Processed {summary.block_count} blocks in {summary.total_duration_seconds:.2f}s")
+ """
+ result = con.sql("""
+ SELECT
+ MIN(start_time) AS start_time,
+ MAX(end_time) AS end_time,
+ EXTRACT(EPOCH FROM (MAX(end_time) - MIN(start_time))) AS total_duration_seconds,
+ COUNT(*) AS block_count
+ FROM spans
+ WHERE span_name = 'Block' AND scope = 'cosmos-sdk/baseapp'
+ """).fetchone()
+
+ return BlockSummary(
+ start_time=result[0],
+ end_time=result[1],
+ total_duration_seconds=result[2],
+ block_count=result[3],
+ )
+
+
+def block_durations(con: duckdb.DuckDBPyConnection, span_name: str = 'Block') -> pd.DataFrame:
+ """
+ Get duration for each block.
+
+ Args:
+ con: DuckDB connection with spans view loaded
+
+ Returns:
+ DataFrame with columns:
+ - block_number: Sequential block number (1-indexed)
+ - duration_ms: Block duration in milliseconds
+
+ Example:
+ >>> from analysis.read_otel import load_otel_data
+ >>> con = load_otel_data('/path/to/data')
+ >>> df = block_durations(con)
+ >>> df.head()
+ """
+ return con.sql("""
+ SELECT
+ ROW_NUMBER() OVER (ORDER BY start_time) AS block_number,
+ EXTRACT(EPOCH FROM duration) * 1000 AS duration_ms
+ FROM spans
+ WHERE span_name = ? AND scope = 'cosmos-sdk/baseapp'
+ ORDER BY start_time
+ """, params=[span_name]).df()
+
+
+def plot_block_durations(runs: Runs, span_name: str = 'Block') -> go.Figure:
+ """
+ Create a plotly line chart comparing block durations across runs.
+
+ Args:
+ runs: Dictionary mapping run name to DuckDB connection
+
+ Returns:
+ Plotly Figure object with block duration traces for each run
+
+ Example:
+ >>> from analysis.read_otel import load_otel_runs
+ >>> runs = load_otel_runs('/path/to/data')
+ >>> fig = plot_block_durations(runs)
+ >>> fig.show()
+ """
+ fig = go.Figure()
+
+ for run_name, con in runs.items():
+ df = block_durations(con, span_name)
+ fig.add_trace(go.Scatter(
+ x=df['block_number'],
+ y=df['duration_ms'],
+ mode='lines',
+ name=run_name,
+ line=dict(width=2)
+ ))
+
+ fig.update_layout(
+ title=f'{span_name} Duration Comparison',
+ xaxis_title='Block Number',
+ yaxis_title='Duration (ms)',
+ hovermode='x unified',
+ template='plotly_white'
+ )
+
+ return fig
\ No newline at end of file
diff --git a/analysis/read_otel.py b/analysis/read_otel.py
new file mode 100644
index 000000000000..179e2c3e9cd7
--- /dev/null
+++ b/analysis/read_otel.py
@@ -0,0 +1,128 @@
+"""Load OpenTelemetry JSONL data into DuckDB views."""
+
+import duckdb
+from pathlib import Path
+from typing import Dict
+
+
+def load_otel_data(data_path: str | Path) -> duckdb.DuckDBPyConnection:
+ """
+ Load OpenTelemetry data from a directory containing trace.jsonl, logs.jsonl, and metrics.jsonl.
+
+ Creates three views in the returned DuckDB connection:
+ - spans: flattened trace data
+ - logs: flattened log data
+ - metrics: flattened metrics data
+
+ Args:
+ data_path: Path to directory containing the JSONL files
+
+ Returns:
+ DuckDB connection with views created
+
+ Example:
+ >>> con = load_otel_data('/path/to/data')
+ >>> con.sql("SELECT span_name, count(*) FROM spans GROUP BY span_name").show()
+ """
+ data_path = Path(data_path)
+ con = duckdb.connect(':memory:')
+
+ # Create spans view from trace.jsonl
+ trace_file = data_path / 'trace.jsonl'
+ if trace_file.exists():
+ con.execute(f"""
+ CREATE VIEW spans AS
+ SELECT
+ Name AS span_name,
+ SpanContext.TraceID AS trace_id,
+ SpanContext.SpanID AS span_id,
+ Parent.SpanID AS parent_span_id,
+ CAST(StartTime AS TIMESTAMPTZ) AS start_time,
+ CAST(EndTime AS TIMESTAMPTZ) AS end_time,
+ CAST(EndTime AS TIMESTAMPTZ) - CAST(StartTime AS TIMESTAMPTZ) AS duration,
+ InstrumentationScope.Name AS scope,
+ ChildSpanCount AS child_span_count,
+ Attributes,
+ Resource,
+ Status
+ FROM read_ndjson_auto('{trace_file}')
+ """)
+
+ # Create logs view from logs.jsonl
+ logs_file = data_path / 'logs.jsonl'
+ if logs_file.exists():
+ con.execute(f"""
+ CREATE VIEW logs AS
+ SELECT
+ CAST(Timestamp AS TIMESTAMPTZ) AS timestamp,
+ CAST(ObservedTimestamp AS TIMESTAMPTZ) AS observed_timestamp,
+ Severity AS severity,
+ SeverityText AS severity_text,
+ Body.Value AS body,
+ TraceID AS trace_id,
+ SpanID AS span_id,
+ Attributes,
+ Resource,
+ Scope
+ FROM read_ndjson_auto('{logs_file}')
+ """)
+
+ # Create metrics view from metrics.jsonl (if file exists and has data)
+ metrics_file = data_path / 'metrics.jsonl'
+ if metrics_file.exists() and metrics_file.stat().st_size > 0:
+ # Flatten the deeply nested metrics structure
+ # Structure: Resource → ScopeMetrics[] → Metrics[] → Data → DataPoints[]
+ con.execute(f"""
+ CREATE VIEW metrics AS
+ SELECT
+ scope_metric.Scope.Name AS scope_name,
+ scope_metric.Scope.Version AS scope_version,
+ metric.Name AS metric_name,
+ metric.Description AS metric_description,
+ metric.Unit AS unit,
+ metric.Data.Temporality AS temporality,
+ metric.Data.IsMonotonic AS is_monotonic,
+ CAST(dp.Time AS TIMESTAMPTZ) AS time,
+ CAST(dp.StartTime AS TIMESTAMPTZ) AS start_time,
+ dp.Value AS value,
+ dp.Count AS count,
+ dp.Sum AS sum,
+ dp.Min AS min,
+ dp.Max AS max,
+ dp.Attributes AS attributes,
+ raw.Resource AS resource
+ FROM read_ndjson_auto('{metrics_file}') AS raw
+ CROSS JOIN UNNEST(raw.ScopeMetrics) AS t(scope_metric)
+ CROSS JOIN UNNEST(scope_metric.Metrics) AS t2(metric)
+ CROSS JOIN UNNEST(metric.Data.DataPoints) AS t3(dp)
+ """)
+
+ return con
+
+
+def load_otel_runs(runs_dir: str | Path) -> Dict[str, duckdb.DuckDBPyConnection]:
+ """
+ Load OpenTelemetry data from multiple run directories.
+
+ Args:
+ runs_dir: Path to directory containing subdirectories with OTEL data
+ Each subdirectory should contain trace.jsonl, logs.jsonl, metrics.jsonl
+
+ Returns:
+ Dictionary mapping run name (subdirectory name) to DuckDB connection
+
+ Example:
+ >>> connections = load_otel_runs('/Users/arc/iavl-bench-data/sims')
+ >>> # Returns: {'iavlx': , 'iavl1': }
+ >>> connections['iavlx'].sql("SELECT count(*) FROM spans").show()
+ """
+ runs_dir = Path(runs_dir)
+ connections = {}
+
+ for subdir in runs_dir.iterdir():
+ if subdir.is_dir():
+ # Check if it has trace.jsonl to confirm it's a valid run directory
+ if (subdir / 'trace.jsonl').exists():
+ connections[subdir.name] = load_otel_data(subdir)
+
+ return connections
\ No newline at end of file
diff --git a/baseapp/abci.go b/baseapp/abci.go
index f7ede1c840f6..c0ff059017d7 100644
--- a/baseapp/abci.go
+++ b/baseapp/abci.go
@@ -106,10 +106,13 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha
// add block gas meter for any genesis transactions (allow infinite gas)
finalizeState.SetContext(finalizeState.Context().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()))
- res, err := app.abciHandlers.InitChainer(finalizeState.Context(), req)
+ ctx := finalizeState.Context()
+ ctx, span := ctx.StartSpan(tracer, "InitChain")
+ res, err := app.abciHandlers.InitChainer(ctx, req)
if err != nil {
return nil, err
}
+ span.End()
if len(req.Validators) > 0 {
if len(req.Validators) != len(res.Validators) {
@@ -152,7 +155,10 @@ func (app *BaseApp) Info(_ *abci.RequestInfo) (*abci.ResponseInfo, error) {
// Query implements the ABCI interface. It delegates to CommitMultiStore if it
// implements Queryable.
-func (app *BaseApp) Query(_ context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) {
+func (app *BaseApp) Query(ctx context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) {
+ ctx, span := tracer.Start(ctx, "Query")
+ defer span.End()
+
// add panic recovery for all queries
//
// Ref: https://github.com/cosmos/cosmos-sdk/pull/8039
@@ -342,6 +348,9 @@ func (app *BaseApp) ApplySnapshotChunk(req *abci.RequestApplySnapshotChunk) (*ab
// will contain relevant error information. Regardless of tx execution outcome,
// the ResponseCheckTx will contain the relevant gas execution context.
func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) {
+ _, span := tracer.Start(context.Background(), "CheckTx")
+ defer span.End()
+
var mode sdk.ExecMode
switch req.Type {
@@ -454,7 +463,10 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc
}
}()
- resp, err = app.abciHandlers.PrepareProposalHandler(prepareProposalState.Context(), req)
+ ctx := prepareProposalState.Context()
+ ctx, span := ctx.StartSpan(tracer, "PrepareProposal")
+ defer span.End()
+ resp, err = app.abciHandlers.PrepareProposalHandler(ctx, req)
if err != nil {
app.logger.Error("failed to prepare proposal", "height", req.Height, "time", req.Time, "err", err)
return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil
@@ -513,7 +525,10 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc
}
processProposalState := app.stateManager.GetState(execModeProcessProposal)
- processProposalState.SetContext(app.getContextForProposal(processProposalState.Context(), req.Height).
+ ctx := processProposalState.Context()
+ ctx, span := ctx.StartSpan(tracer, "ProcessProposal")
+ defer span.End()
+ processProposalState.SetContext(app.getContextForProposal(ctx, req.Height).
WithVoteInfos(req.ProposedLastCommit.Votes). // this is a set of votes that are not finalized yet, wait for commit
WithBlockHeight(req.Height).
WithBlockTime(req.Time).
@@ -595,6 +610,9 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (
return nil, errors.New("application ExtendVote handler not set")
}
+ ctx, span := ctx.StartSpan(tracer, "ExtendVote")
+ defer span.End()
+
// If vote extensions are not enabled, as a safety precaution, we return an
// error.
cp := app.GetConsensusParams(ctx)
@@ -666,6 +684,9 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
ctx = sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager)
}
+ ctx, span := ctx.StartSpan(tracer, "VerifyVoteExtension")
+ defer span.End()
+
// If vote extensions are not enabled, as a safety precaution, we return an
// error.
cp := app.GetConsensusParams(ctx)
@@ -716,7 +737,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r
// Execution flow or by the FinalizeBlock ABCI method. The context received is
// only used to handle early cancellation, for anything related to state app.stateManager.GetState(execModeFinalize).Context()
// must be used.
-func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
+func (app *BaseApp) internalFinalizeBlock(goCtx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
var events []abci.Event
if err := app.checkHalt(req.Height, req.Time); err != nil {
@@ -750,7 +771,6 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
app.stateManager.SetState(execModeFinalize, app.cms, header, app.logger, app.streamingManager)
finalizeState = app.stateManager.GetState(execModeFinalize)
}
-
// Context is now updated with Header information.
finalizeState.SetContext(finalizeState.Context().
WithBlockHeader(header).
@@ -782,6 +802,16 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
WithHeaderHash(req.Hash))
}
+ // instrument FinalizeBlock here and capture block context, because we'll switch back to that after FinalizeBlock
+ blockCtx := finalizeState.Context()
+ ctx, span := blockCtx.StartSpan(tracer, "FinalizeBlock")
+ finalizeState.SetContext(ctx)
+ defer span.End()
+ defer func() {
+ // restore the go context to block context so that Commit is not instrumented as a child of FinalizeBlock
+ finalizeState.SetContext(finalizeState.Context().WithContext(blockCtx.Context()))
+ }()
+
preblockEvents, err := app.preBlock(req)
if err != nil {
return nil, err
@@ -796,9 +826,11 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
// First check for an abort signal after beginBlock, as it's the first place
// we spend any significant amount of time.
+ // NOTE: we use the go context here because that is used for cancellation
+ // whereas the finalize state context is used for block execution
select {
- case <-ctx.Done():
- return nil, ctx.Err()
+ case <-goCtx.Done():
+ return nil, goCtx.Err()
default:
// continue
}
@@ -846,15 +878,17 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request
WithBlockGasUsed(blockGasUsed).
WithBlockGasWanted(blockGasWanted),
)
- endBlock, err := app.endBlock(finalizeState.Context())
+ endBlock, err := app.endBlock()
if err != nil {
return nil, err
}
// check after endBlock if we should abort, to avoid propagating the result
+ // NOTE: we use the go context here because that is used for cancellation
+ // whereas the finalize state context is used for block execution
select {
- case <-ctx.Done():
- return nil, ctx.Err()
+ case <-goCtx.Done():
+ return nil, goCtx.Err()
default:
// continue
}
@@ -959,7 +993,11 @@ func (app *BaseApp) checkHalt(height int64, time time.Time) error {
// height.
func (app *BaseApp) Commit() (*abci.ResponseCommit, error) {
finalizeState := app.stateManager.GetState(execModeFinalize)
- header := finalizeState.Context().BlockHeader()
+ ctx := finalizeState.Context()
+ ctx, span := ctx.StartSpan(tracer, "Commit")
+ defer span.End()
+
+ header := ctx.BlockHeader()
retainHeight := app.GetBlockRetentionHeight(header.Height)
if app.abciHandlers.Precommiter != nil {
@@ -1005,6 +1043,12 @@ func (app *BaseApp) Commit() (*abci.ResponseCommit, error) {
// The SnapshotIfApplicable method will create the snapshot by starting the goroutine
app.snapshotManager.SnapshotIfApplicable(header.Height)
+ // track metrics and setup span for next block
+ // TODO: should this be in the state manager instead?
+ blockCnt.Add(ctx, 1)
+ blockTime.Record(ctx, time.Since(app.blockStartTime).Seconds())
+ app.blockStartTime = time.Now()
+
return resp, nil
}
diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go
index 31fd20b1f7e7..2d0362b467f5 100644
--- a/baseapp/baseapp.go
+++ b/baseapp/baseapp.go
@@ -1,13 +1,16 @@
package baseapp
+// need to import telemetry before anything else for side effects
+import _ "github.com/cosmos/cosmos-sdk/telemetry"
+
import (
- "context"
"fmt"
"maps"
"math"
"slices"
"strconv"
"sync"
+ "time"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
@@ -15,15 +18,20 @@ import (
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
+ "go.opentelemetry.io/otel/trace"
protov2 "google.golang.org/protobuf/proto"
errorsmod "cosmossdk.io/errors"
- "cosmossdk.io/log"
"cosmossdk.io/store"
storemetrics "cosmossdk.io/store/metrics"
"cosmossdk.io/store/snapshots"
storetypes "cosmossdk.io/store/types"
+ "cosmossdk.io/log"
+
"github.com/cosmos/cosmos-sdk/baseapp/config"
"github.com/cosmos/cosmos-sdk/baseapp/oe"
"github.com/cosmos/cosmos-sdk/baseapp/state"
@@ -59,6 +67,41 @@ const (
var _ servertypes.ABCI = (*BaseApp)(nil)
+var (
+ tracer = otel.Tracer("cosmos-sdk/baseapp")
+ meter = otel.Meter("cosmos-sdk/baseapp")
+ blockCnt metric.Int64Counter
+ txCnt metric.Int64Counter
+ blockTime metric.Float64Histogram
+ txTime metric.Int64Histogram
+)
+
+func init() {
+ var err error
+ blockCnt, err = meter.Int64Counter("block.count")
+ if err != nil {
+ panic(err)
+ }
+ txCnt, err = meter.Int64Counter("tx.count")
+ if err != nil {
+ panic(err)
+ }
+ blockTime, err = meter.Float64Histogram("block.time",
+ metric.WithUnit("s"),
+ metric.WithDescription("Block time in seconds"),
+ )
+ if err != nil {
+ panic(err)
+ }
+ txTime, err = meter.Int64Histogram("tx.time",
+ metric.WithUnit("us"),
+ metric.WithDescription("Transaction time in microseconds"),
+ )
+ if err != nil {
+ panic(err)
+ }
+}
+
// BaseApp reflects the ABCI application implementation.
type BaseApp struct {
// initialized on creation
@@ -164,6 +207,8 @@ type BaseApp struct {
// Optional alternative tx runner, used for block-stm parallel transaction execution. If nil, default txRunner is used.
txRunner sdk.TxRunner
+
+ blockStartTime time.Time
}
// NewBaseApp returns a reference to an initialized BaseApp. It accepts a
@@ -184,8 +229,11 @@ func NewBaseApp(
fauxMerkleMode: false,
sigverifyTx: true,
gasConfig: config.GasConfig{QueryGasLimit: math.MaxUint64},
+ blockStartTime: time.Now(),
}
+ // initialize tracer
+
for _, option := range options {
option(app)
}
@@ -656,6 +704,8 @@ func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, erro
if app.abciHandlers.PreBlocker != nil {
finalizeState := app.stateManager.GetState(execModeFinalize)
ctx := finalizeState.Context().WithEventManager(sdk.NewEventManager())
+ ctx, span := ctx.StartSpan(tracer, "preBlock")
+ defer span.End()
rsp, err := app.abciHandlers.PreBlocker(ctx, req)
if err != nil {
return nil, err
@@ -681,7 +731,10 @@ func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, er
)
if app.abciHandlers.BeginBlocker != nil {
- resp, err = app.abciHandlers.BeginBlocker(app.stateManager.GetState(execModeFinalize).Context())
+ ctx := app.stateManager.GetState(execModeFinalize).Context()
+ ctx, span := ctx.StartSpan(tracer, "beginBlock")
+ defer span.End()
+ resp, err = app.abciHandlers.BeginBlocker(ctx)
if err != nil {
return resp, err
}
@@ -739,11 +792,14 @@ func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txI
// endBlock is an application-defined function that is called after transactions
// have been processed in FinalizeBlock.
-func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) {
+func (app *BaseApp) endBlock() (sdk.EndBlock, error) {
var endblock sdk.EndBlock
if app.abciHandlers.EndBlocker != nil {
- eb, err := app.abciHandlers.EndBlocker(app.stateManager.GetState(execModeFinalize).Context())
+ ctx := app.stateManager.GetState(execModeFinalize).Context()
+ ctx, span := ctx.StartSpan(tracer, "endBlock")
+ defer span.End()
+ eb, err := app.abciHandlers.EndBlocker(ctx)
if err != nil {
return endblock, err
}
@@ -773,12 +829,16 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) {
// both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice
// passing the decoded tx to runTX is optional, it will be decoded if the tx is nil
func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
+ startTime := time.Now()
+ ctx := app.getContextForTx(mode, txBytes, txIndex)
+ ctx, span := ctx.StartSpan(tracer, "runTx")
+ defer span.End()
+
// NOTE: GasWanted should be returned by the AnteHandler. GasUsed is
// determined by the GasMeter. We need access to the context to get the gas
// meter, so we initialize upfront.
var gasWanted uint64
- ctx := app.getContextForTx(mode, txBytes, txIndex)
if incarnationCache != nil {
ctx = ctx.WithIncarnationCache(incarnationCache)
}
@@ -861,7 +921,11 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex
// performance benefits, but it'll be more difficult to get right.
anteCtx, msCache = app.cacheTxContext(ctx, txBytes)
anteCtx = anteCtx.WithEventManager(sdk.NewEventManager())
+ anteCtx, anteSpan := anteCtx.StartSpan(tracer, "anteHandler")
newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate)
+ anteSpan.End()
+ // now we should back to the previous go context which didn't capture the anteHandler instrumentation span
+ newCtx = newCtx.WithContext(ctx)
if !newCtx.IsZero() {
// At this point, newCtx.MultiStore() is a store branch, or something else
@@ -951,6 +1015,9 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex
consumeBlockGas()
msCache.Write()
+
+ txCnt.Add(ctx, 1)
+ txTime.Record(ctx, time.Since(startTime).Microseconds())
}
if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) {
@@ -968,6 +1035,9 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex
// Handler does not exist for a given message route. Otherwise, a reference to a
// Result is returned. The caller must not commit state if an error is returned.
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode sdk.ExecMode) (*sdk.Result, error) {
+ ctx, span := ctx.StartSpan(tracer, "runMsgs")
+ defer span.End()
+
events := sdk.EmptyEvents()
var msgResponses []*codectypes.Any
@@ -977,15 +1047,26 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me
break
}
- ctx = ctx.WithMsgIndex(i)
+ msgCtx := ctx.WithMsgIndex(i)
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
+ msgTypeUrl := sdk.MsgTypeURL(msg)
+ // we create two spans here for easy visualization in trace logs, this can be removed later if deemed unnecessary
+ msgCtx, msgSpan := msgCtx.StartSpan(tracer, "msgHandler",
+ trace.WithAttributes(
+ attribute.String("msg_type", msgTypeUrl),
+ attribute.Int("msg_index", i),
+ ),
+ )
+ msgCtx, msgSpan2 := msgCtx.StartSpan(tracer, fmt.Sprintf("msgHandler.%s", msgTypeUrl))
// ADR 031 request type routing
msgResult, err := handler(ctx, msg)
+ msgSpan2.End()
+ msgSpan.End()
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i)
}
@@ -1019,7 +1100,6 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me
}
msgResponses = append(msgResponses, msgResponse)
}
-
}
data, err := makeABCIData(msgResponses)
diff --git a/baseapp/state/manager.go b/baseapp/state/manager.go
index fb8cb55e730f..e17b55547310 100644
--- a/baseapp/state/manager.go
+++ b/baseapp/state/manager.go
@@ -5,6 +5,9 @@ import (
"sync"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+ "go.opentelemetry.io/otel"
+ otelattr "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/trace"
"cosmossdk.io/core/header"
"cosmossdk.io/log"
@@ -14,6 +17,10 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
+var (
+ tracer = otel.Tracer("cosmos-sdk/baseapp")
+)
+
type Manager struct {
// volatile states:
//
@@ -107,6 +114,17 @@ func (mgr *Manager) SetState(
mgr.processProposalState = baseState
case sdk.ExecModeFinalize:
+ // add tracing span instrumentation here when the context is initialized for the block
+ height := h.Height
+ if height == 0 {
+ height = 1
+ }
+ baseState.ctx, baseState.span = baseState.ctx.StartSpan(tracer, "Block",
+ trace.WithAttributes(
+ otelattr.Int64("height", height),
+ otelattr.Int64("time_unix_nano", h.Time.UnixNano()),
+ ),
+ )
mgr.finalizeBlockState = baseState
default:
@@ -129,6 +147,10 @@ func (mgr *Manager) ClearState(mode sdk.ExecMode) {
mgr.processProposalState = nil
case sdk.ExecModeFinalize:
+ // complete tracing span instrumentation here when the context is cleared for the block
+ if mgr.finalizeBlockState != nil {
+ mgr.finalizeBlockState.span.End()
+ }
mgr.finalizeBlockState = nil
default:
diff --git a/baseapp/state/state.go b/baseapp/state/state.go
index fedfe5b1fd62..7655e8f83c12 100644
--- a/baseapp/state/state.go
+++ b/baseapp/state/state.go
@@ -4,6 +4,7 @@ import (
"sync"
storetypes "cosmossdk.io/store/types"
+ "go.opentelemetry.io/otel/trace"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@@ -13,6 +14,8 @@ type State struct {
mtx sync.RWMutex
ctx sdk.Context
+
+ span trace.Span
}
func NewState(ctx sdk.Context, ms storetypes.CacheMultiStore) *State {
diff --git a/client/v2/go.mod b/client/v2/go.mod
index e37d6f682dad..b27bfcba5b27 100644
--- a/client/v2/go.mod
+++ b/client/v2/go.mod
@@ -30,7 +30,6 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
- github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
@@ -38,6 +37,7 @@ require (
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
@@ -63,6 +63,8 @@ require (
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/emicklei/dot v1.8.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -73,6 +75,7 @@ require (
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
@@ -83,11 +86,14 @@ require (
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
+ github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
@@ -108,6 +114,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.10.3 // indirect
+ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -120,16 +127,19 @@ require (
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.3 // indirect
- github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/prometheus/otlptranslator v0.0.2 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sasha-s/go-deadlock v0.3.6 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.7 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -139,15 +149,38 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect
+ github.com/tklauser/go-sysconf v0.3.15 // indirect
+ github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v1.0.1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect
+ go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
diff --git a/client/v2/go.sum b/client/v2/go.sum
index 28cd742566dc..bcc7db499dfd 100644
--- a/client/v2/go.sum
+++ b/client/v2/go.sum
@@ -29,7 +29,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -89,6 +88,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -199,6 +200,9 @@ github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI=
github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
@@ -253,6 +257,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
@@ -346,6 +353,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -354,6 +363,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
@@ -456,6 +467,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4=
github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -572,6 +585,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -598,6 +613,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
+github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -605,8 +622,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
-github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -631,6 +648,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
+github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -688,6 +707,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -699,6 +722,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw=
github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
@@ -714,17 +739,55 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw=
+go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8=
+go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
+go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
+go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
+go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -837,6 +900,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -853,6 +917,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -872,6 +937,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/go.mod b/go.mod
index af11e9186b28..211df225b4cc 100644
--- a/go.mod
+++ b/go.mod
@@ -25,8 +25,10 @@ require (
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/gogogateway v1.2.0
github.com/cosmos/gogoproto v1.7.2
+ github.com/cosmos/iavl v1.2.6
github.com/cosmos/ledger-cosmos-go v0.16.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
+ github.com/edsrzf/mmap-go v1.0.0
github.com/golang/protobuf v1.5.4
github.com/google/go-cmp v0.7.0
github.com/google/gofuzz v1.2.0
@@ -46,8 +48,6 @@ require (
github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-isatty v0.0.20
github.com/mdp/qrterminal/v3 v3.2.1
- github.com/prometheus/client_golang v1.23.2
- github.com/prometheus/common v0.67.3
github.com/rs/zerolog v1.34.0
github.com/spf13/cast v1.10.0
github.com/spf13/cobra v1.10.1
@@ -57,8 +57,24 @@ require (
github.com/tendermint/go-amino v0.16.0
github.com/test-go/testify v1.1.4
github.com/tidwall/btree v1.8.1
+ go.opentelemetry.io/contrib/bridges/otelslog v0.13.0
+ go.opentelemetry.io/contrib/instrumentation/host v0.63.0
+ go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0
+ go.opentelemetry.io/contrib/otelconf v0.18.0
+ go.opentelemetry.io/otel v1.38.0
+ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0
+ go.opentelemetry.io/otel/log v0.14.0
+ go.opentelemetry.io/otel/metric v1.38.0
+ go.opentelemetry.io/otel/sdk v1.38.0
+ go.opentelemetry.io/otel/sdk/log v0.14.0
+ go.opentelemetry.io/otel/sdk/metric v1.38.0
+ go.opentelemetry.io/otel/trace v1.38.0
go.uber.org/mock v0.6.0
+ go.yaml.in/yaml/v3 v3.0.4
golang.org/x/crypto v0.44.0
+ golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
golang.org/x/sync v0.18.0
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8
google.golang.org/grpc v1.77.0
@@ -80,7 +96,6 @@ require (
cosmossdk.io/schema v1.1.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
- github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
@@ -110,6 +125,7 @@ require (
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect
@@ -119,7 +135,6 @@ require (
github.com/cockroachdb/redact v1.1.6 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
- github.com/cosmos/iavl v1.2.6 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -128,6 +143,7 @@ require (
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
github.com/emicklei/dot v1.8.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
@@ -141,6 +157,7 @@ require (
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
@@ -154,6 +171,8 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
@@ -171,6 +190,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.10.3 // indirect
+ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
@@ -184,21 +204,29 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
+ github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/otlptranslator v0.0.2 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sasha-s/go-deadlock v0.3.6 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.7 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/supranational/blst v0.3.16 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
+ github.com/tklauser/go-sysconf v0.3.15 // indirect
+ github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v1.0.1 // indirect
@@ -207,17 +235,19 @@ require (
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
- go.opentelemetry.io/otel v1.38.0 // indirect
- go.opentelemetry.io/otel/metric v1.38.0 // indirect
- go.opentelemetry.io/otel/sdk v1.38.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
- go.opentelemetry.io/otel/trace v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.21.0 // indirect
- golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sys v0.38.0 // indirect
diff --git a/go.sum b/go.sum
index b9b97fa75a76..44144084e8a2 100644
--- a/go.sum
+++ b/go.sum
@@ -51,7 +51,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -159,6 +158,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -274,6 +275,9 @@ github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI=
github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
@@ -338,6 +342,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
@@ -439,6 +446,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -447,6 +456,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow=
@@ -559,6 +570,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4=
github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -678,6 +691,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -704,6 +719,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
+github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -711,8 +728,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
-github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -737,6 +754,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
+github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -801,6 +820,10 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -814,6 +837,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw=
github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
@@ -829,25 +854,61 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw=
+go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8=
+go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
+go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
+go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
+go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -975,6 +1036,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -992,6 +1054,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1011,6 +1074,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/iavlx/README.md b/iavlx/README.md
new file mode 100644
index 000000000000..5905be10f32c
--- /dev/null
+++ b/iavlx/README.md
@@ -0,0 +1,147 @@
+# iavl
+
+## Code Organization
+
+### Node Types, Memory & Disk Layouts
+
+Much of this code was influenced by memiavl and sometimes even copied directly from it.
+The `NodeID` design is mainly from iavl/v2.
+The `NodePointer` design introduces the possibility of doing node eviction similar to iavl/v2,
+but with non-blocking thread safety using `atomic.Pointer` so that eviction can happen in the background without
+blocking reads or writes.
+
+* `node.go`: the `Node` interface which all 3 node types implement (`MemNode`, `BranchPersisted`, `LeafPersisted`)
+* `mem_node.go`: in-memory node structure, new nodes always use the `MemNode` type
+* `node_pointer.go`: all child references are wrapped in `NodePointer` which can point to either an in-memory node or an
+ on-disk node, or both (if the node has been written and node evicted)
+* `node_id.go`: defines `NodeID` (version + index + leaf) and `NodeRef` (either a `NodeID` or a node offset in the
+ changeset file)
+* `branch_layout.go`: defines the on-disk layout for branch nodes
+* `leaf_layout.go`: defines the on-disk layout for leaf nodes
+* `branch_persisted.go`: a wrapper around `BranchLayout` which implements the `Node` interface and also tracks a store
+ reference
+* `leaf_persisted.go`: a wrapper around `LeafLayout` which implements the `Node` interface and also tracks a store
+ reference
+
+### Tree Management & Updating
+
+For managing tree state, we define two core types `Tree` and `CommitTree`.
+We directly read from and apply updates to `Tree`s but these updates only affect the persistent state of the tree if
+they are applied and committed to a `CommitTree`.
+
+* `tree.go`: a `Tree` struct which implements the Cosmos SDK `KVStore` interface and implements the key methods (get,
+ set,
+ delete, commit, etc). `Tree`s can be mutated, and changes can either be committed or discarded. This is essentially an
+ in-memory reference to a tree at a specific version that could be used read-only or mutated ad hoc without affecting
+ the underlying persistent tree (say for instance in `CheckTx`).
+* `commit_tree.go`: defines the `CommitTree` structure which manages the persistent tree state. Using `CommitTree` you
+ can
+ create new mutable `Tree` instance using `Branch` and decide to `Apply` its changes to the persistent tree or discard
+ them. Calling `Commit` flushes changes to the underlying `TreeStore` which does all of the on disk state management
+ and cleanup. In `CommitTree` we also have an asynchronous WAL writing process (optional) and maintain a background
+ eviction process.
+* `update.go`: types for batching changes which can later be commited or discarded
+* `node_update.go` and : the code for setting and deleting nodes and doing tree rebalancing, adapted from memiavl and
+ iavl/v1
+* `node_hash.go`: code for computing node hashes, adapted from memiavl and iavl/v1
+* `iterator.go`: implements the Cosmos SDK `Iterator` interface, adapted from memiavl and iavl/v1
+
+### Disk State Management
+
+### Central Coordination
+
+These files are the central core of managing on-disk state across multiple changesets which may be in the process of
+being written or compacted. **This is the most complex part of the codebase.**
+
+* `tree_store.go`: code for dispatching read operations to the correct changeset, writing commits to new changesets,
+ and coordinating background compaction and cleanup of old changesets
+* `cleanup.go`: the actual background cleanup and compaction thread
+
+#### Changeset Reading, Writing and Compaction
+
+* `changeset_files.go`: `ChangesetFiles` represents the five files which make up a changeset:
+ * `kv.log`: all of the key/value pairs in the changeset, and optionally the write-ahead log for replay (this is
+ configurable)
+ * `leaves.dat`: an array of `LeafLayout` structs
+ * `branches.dat`: an array of `BranchLayout` structs
+ * `verions.dat`: an array of `VersionInfo` structs, one for each version in the changeset
+ * `info.dat`: a single `ChangesetInfo` struct which tracks metadata about the changeset including the range of
+ versions
+ it contains and the number of orphaned nodes
+* `changeset.go`: the `Changeset` struct wraps mmap's of the five changeset files and provides
+ methods for reading nodes from disk and marking them as orphaned. It includes some complex code for safely disposing
+ of `Changeset` instances because we need to either 1) reopen the memmap to change its size, or 2) close the
+ `Changeset` because it has been compacted and will be deleted. This is managed using pinning, a reference count, and
+ atomic booleans to track eviction (the desire to dispose and delete) and disposal (the actual disposal).
+* `changeset_writer.go`: code for iteratively writing changesets to disk node by node in post-order traversal order.
+ Node references can either be by
+ `NodeID` or offsets (offsets have been disabled due to some unresolved bugs)
+* `compactor.go`: code for rewriting one or more changesets into a new compacted changeset, skipping
+ orphaned nodes and updating offsets as needed (this offset rewrite code is currently buggy and disabled)
+
+#### Helpers
+
+* `version_info.go`: defines the on-disk layout for version info records, which track the root node and other metadata
+ for
+ each version
+* `changeset_info.go`: defines the on-disk layout for the changeset info record, which tracks metadata
+ about the entire changeset including version range and number of orphaned nodes
+* `kvlog.go`: code for reading key/value pairs from the `kv.log` file
+* `kvlog_writer.go`: code for writing key/value pairs to the `kv.log` file, which can be structured as a write-ahead
+ operation log for replay and crash recovery (reply and recovery aren't implemented yet)
+* `mmap.go`: the `MmapFile` mem-map wrapper
+* `writer.go`: `FileWriter` and `StructWriter` wrappers for writing raw bytes and structs to files
+* `reader.go`: `StructMap` and `NodeMap` wrappers for representing memory-mapped arrays of structs and nodes
+
+### Multi-tree Management
+
+* `multi_tree.go`: wraps multiple `Tree`s into a `MultiTree` which provides a mutable way to write a tree without
+ committing the changes to the persistent tree immediately (can be discarded)
+* `commit_multi_tree.go`: wraps multiple `CommitTree`s into a `CommitMultiTree` which provides a way to create mutable
+ `MultiTree`s and commit their changes to the underlying persistent trees (or discard them). This can eventually
+ implement `RootMultiStore` and replace the SDK's store package. `CommitMultiTree` makes the optimization of running
+ `Commit` in parallel across all `CommitTree`s which could improve performance.
+
+### Options
+
+Options are mantained by the `Options` struct in `options.go`. Many options have a getter which uses a default value if
+the option is not set.
+
+The main options we're controlling now are:
+
+* `WriteWAL`: whether we write all updates to the kv-log as a replayable write-ahead log (WAL). If this is enabled we
+ will fsync the WAL either asynchronously or synchronously (based on the `WalSyncBuffer` option). Enabling WAL could
+ actually improve performance because we asynchronously write key/value data in advance of `CommitTree.Commit` being
+ called.
+* `EvictDepth`: the depth of the tree beyond which we will evict nodes from memory as soon as they are on disk. This is
+ the main lever for controlling memory usage. Using more memory could improve performance.
+* `RetainVersions`: the number of recent versions to retain when we are compacting. Eventually we also want to enable
+ some sort of snapshot-based compaction (retaining full trees every N versions).
+* `MinCompactionSeconds`: the minimum number of seconds to wait before starting a new compaction run (note that this
+ currently includes the time it takes to compact).
+* `CompactWAL`: whether to compact the WAL when we are compacting changesets. In the future, we can distinguish between
+ compacting the WAL before our first checkpoint and retaining it after the first checkpoint.
+* `ChangesetMaxTarget`: the size of a changeset after which we will roll over to a new changeset for the next version.
+* `CompactionMaxTarget`: the target size of a compacted changeset. When adding a new changeset into our compaction will
+ stay below this number, we will join multiple changesets into a single compacted changeset.
+* `CompactionOrphanRatio`: the ratio of orphaned nodes in a changeset beyond which we will trigger it for early
+ compaction (used together with `CompactionOrphanAge`)
+* `CompactionOrphanAge`: the average age of orphaned nodes in a changeset beyond which we will trigger it for early
+ compaction (used together with `CompactionOrphanRatio`)
+* `CompactAfterVersions`: the number of versions after which we will trigger a compaction when any orphans are present,
+ measured in versions since the last compaction.
+* `ReaderUpdateInterval`: when writing multiple versions to a changeset, the number of versions after which we will open
+ the changeset for reading even if it has not been completed, so that readers can access the latest versions sooner and
+ flush memory. Set this to a shorter interval if we want to constrain memory usage more tightly and longer if we want
+ to reduce the number of times memmaps are re-opened for reading.
+
+### Utilities
+
+* `dot_graph.go`: code for exporting trees to Graphviz dot graph format for visualization
+* `verify.go`: code for verifying tree integrity
+
+### Tests
+
+* `tree_test.go`: the only tests we have so far. These do, however, use property-based testing so we are generating
+ random operation sets, applying them to both iavlx and iavl/v1 trees. At each step, we confirm that behavior is
+ identical, including verification of hashes and verifying that invariants are maintained.
\ No newline at end of file
diff --git a/iavlx/branch_layout.go b/iavlx/branch_layout.go
new file mode 100644
index 000000000000..4c2c2f0e15fc
--- /dev/null
+++ b/iavlx/branch_layout.go
@@ -0,0 +1,32 @@
+package iavlx
+
+import (
+ "fmt"
+ "unsafe"
+)
+
+func init() {
+ if unsafe.Sizeof(BranchLayout{}) != SizeBranch {
+ panic(fmt.Sprintf("invalid BranchLayout size: got %d, want %d", unsafe.Sizeof(BranchLayout{}), SizeBranch))
+ }
+}
+
+const (
+ SizeBranch = 80
+)
+
+type BranchLayout struct {
+ Id NodeID
+ Left NodeID
+ Right NodeID
+ LeftOffset uint32 // absolute offset
+ RightOffset uint32 // absolute offset
+ KeyOffset uint32
+ Height uint8
+ Size uint32 // TODO 5 bytes? (there are 3 bytes of padding here)
+ Hash [32]byte
+}
+
+func (b BranchLayout) ID() NodeID {
+ return b.Id
+}
diff --git a/iavlx/branch_persisted.go b/iavlx/branch_persisted.go
new file mode 100644
index 000000000000..c5ec0787af77
--- /dev/null
+++ b/iavlx/branch_persisted.go
@@ -0,0 +1,118 @@
+package iavlx
+
+import (
+ "bytes"
+ "sync/atomic"
+)
+
+type BranchPersisted struct {
+ store *Changeset
+ layout BranchLayout
+}
+
+func (node *BranchPersisted) Left() *NodePointer {
+ return &NodePointer{
+ mem: atomic.Pointer[MemNode]{},
+ store: node.store,
+ fileIdx: node.layout.LeftOffset,
+ id: node.layout.Left,
+ }
+}
+
+func (node *BranchPersisted) Right() *NodePointer {
+ return &NodePointer{
+ mem: atomic.Pointer[MemNode]{},
+ store: node.store,
+ fileIdx: node.layout.RightOffset,
+ id: node.layout.Right,
+ }
+}
+
+func (node *BranchPersisted) ID() NodeID {
+ return node.layout.Id
+}
+
+func (node *BranchPersisted) Height() uint8 {
+ return node.layout.Height
+}
+
+func (node *BranchPersisted) IsLeaf() bool {
+ return false
+}
+
+func (node *BranchPersisted) Size() int64 {
+ return int64(node.layout.Size)
+}
+
+func (node *BranchPersisted) Version() uint32 {
+ return uint32(node.layout.Id.Version())
+}
+
+func (node *BranchPersisted) Key() ([]byte, error) {
+ return node.store.ReadK(node.layout.Id, node.layout.KeyOffset)
+}
+
+func (node *BranchPersisted) Value() ([]byte, error) {
+ return nil, nil
+}
+
+func (node *BranchPersisted) Hash() []byte {
+ return node.layout.Hash[:]
+}
+
+func (node *BranchPersisted) SafeHash() []byte {
+ return node.layout.Hash[:]
+}
+
+func (node *BranchPersisted) MutateBranch(version uint32) (*MemNode, error) {
+ key, err := node.Key()
+ if err != nil {
+ return nil, err
+ }
+
+ memNode := &MemNode{
+ height: node.Height(),
+ size: node.Size(),
+ version: version,
+ key: key,
+ left: node.Left(),
+ right: node.Right(),
+ }
+ return memNode, err
+}
+
+func (node *BranchPersisted) Get(key []byte) (value []byte, index int64, err error) {
+ nodeKey, err := node.Key()
+ if err != nil {
+ return nil, 0, err
+ }
+
+ if bytes.Compare(key, nodeKey) < 0 {
+ leftNode, err := node.Left().Resolve()
+ if err != nil {
+ return nil, 0, err
+ }
+
+ return leftNode.Get(key)
+ }
+
+ rightNode, err := node.Right().Resolve()
+ if err != nil {
+ return nil, 0, err
+ }
+
+ value, index, err = rightNode.Get(key)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ index += node.Size() - rightNode.Size()
+ return value, index, nil
+}
+
+func (node *BranchPersisted) String() string {
+ // TODO implement me
+ panic("implement me")
+}
+
+var _ Node = (*BranchPersisted)(nil)
diff --git a/iavlx/cache_tree.go b/iavlx/cache_tree.go
new file mode 100644
index 000000000000..53550442ebaf
--- /dev/null
+++ b/iavlx/cache_tree.go
@@ -0,0 +1,139 @@
+package iavlx
+
+import (
+ io "io"
+ "sync"
+
+ storetypes "cosmossdk.io/store/types"
+ "github.com/cosmos/cosmos-sdk/iavlx/internal"
+)
+
+type CacheTree struct {
+ mtx sync.Mutex // TODO do we really need a mutex or could this be part of the caller contract?
+ parent storetypes.KVStore
+ dirty bool
+ cache internal.BTree
+}
+
+func NewCacheTree(parent storetypes.KVStore) *CacheTree {
+ return &CacheTree{parent: parent, cache: internal.NewBTree()}
+}
+
+func (store *CacheTree) GetStoreType() storetypes.StoreType {
+ return storetypes.StoreTypeIAVL
+}
+
+func (store *CacheTree) CacheWrap() storetypes.CacheWrap {
+ return NewCacheTree(store)
+}
+
+func (store *CacheTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap {
+ // TODO implement tracing
+ return NewCacheTree(store)
+}
+
+func (store *CacheTree) Get(key []byte) (value []byte) {
+ store.mtx.Lock()
+ defer store.mtx.Unlock()
+
+ storetypes.AssertValidKey(key)
+
+ var ok bool
+ value, ok = store.cache.Get(key)
+ if !ok {
+ value = store.parent.Get(key)
+ store.cache.SetCached(key, value)
+ }
+ return value
+}
+
+func (store *CacheTree) Has(key []byte) bool {
+ value := store.Get(key)
+ return value != nil
+}
+
+func (store *CacheTree) Set(key, value []byte) {
+ storetypes.AssertValidKey(key)
+ storetypes.AssertValidValue(value)
+
+ store.mtx.Lock()
+ defer store.mtx.Unlock()
+ store.cache.Set(key, value)
+ store.dirty = true
+}
+
+func (store *CacheTree) Delete(key []byte) {
+ storetypes.AssertValidKey(key)
+
+ store.mtx.Lock()
+ defer store.mtx.Unlock()
+
+ store.cache.Delete(key)
+ store.dirty = true
+}
+
+func (store *CacheTree) Iterator(start, end []byte) storetypes.Iterator {
+ return store.iterator(start, end, true)
+}
+
+func (store *CacheTree) ReverseIterator(start, end []byte) storetypes.Iterator {
+ return store.iterator(start, end, false)
+}
+
+func (store *CacheTree) Write() {
+ store.mtx.Lock()
+ defer store.mtx.Unlock()
+
+ if !store.dirty {
+ return
+ }
+
+ // TODO if we are concerned about retaining the whole tree in memory, we could maybe drain the cache using Map.PopMin
+ store.cache.Scan(func(key, value []byte, dirty bool) bool {
+ if !dirty {
+ // TODO we could save these cached reads in the tree but for now we just clear the whole cache
+ return true
+ }
+
+ // We use []byte(key) instead of conv.UnsafeStrToBytes because we cannot
+ // be sure if the underlying store might do a save with the byteslice or
+ // not. Once we get confirmation that .Delete is guaranteed not to
+ // save the byteslice, then we can assume only a read-only copy is sufficient.
+ if value == nil {
+ store.parent.Delete(key)
+ } else {
+ store.parent.Set(key, value)
+ }
+ return true
+ })
+
+ store.cache.Clear()
+ store.dirty = false
+}
+
+func (store *CacheTree) iterator(start, end []byte, ascending bool) storetypes.Iterator {
+ store.mtx.Lock()
+ defer store.mtx.Unlock()
+
+ isoSortedCache := store.cache.Copy()
+
+ var (
+ err error
+ parent, cache storetypes.Iterator
+ )
+
+ if ascending {
+ parent = store.parent.Iterator(start, end)
+ cache, err = isoSortedCache.Iterator(start, end)
+ } else {
+ parent = store.parent.ReverseIterator(start, end)
+ cache, err = isoSortedCache.ReverseIterator(start, end)
+ }
+ if err != nil {
+ panic(err)
+ }
+
+ return internal.NewCacheMergeIterator(parent, cache, ascending)
+}
+
+var _ storetypes.CacheKVStore = (*CacheTree)(nil)
diff --git a/iavlx/changeset.go b/iavlx/changeset.go
new file mode 100644
index 000000000000..9f872924f102
--- /dev/null
+++ b/iavlx/changeset.go
@@ -0,0 +1,379 @@
+package iavlx
+
+import (
+ "errors"
+ "fmt"
+ "sync/atomic"
+ "unsafe"
+)
+
+type Changeset struct {
+ files *ChangesetFiles
+ info *ChangesetInfo // we copy this so that it can be accessed even in shared changesets
+
+ treeStore *TreeStore
+
+ kvLog *KVLog // TODO make sure we handle compaction here too
+ branchesData *NodeMmap[BranchLayout]
+ leavesData *NodeMmap[LeafLayout]
+ versionsData *StructMmap[VersionInfo]
+ orphanWriter *OrphanWriter
+
+ refCount atomic.Int32
+ evicted atomic.Bool
+ disposed atomic.Bool
+ dirtyBranches atomic.Bool
+ dirtyLeaves atomic.Bool
+ needsSync atomic.Bool
+}
+
+func NewChangeset(treeStore *TreeStore) *Changeset {
+ return &Changeset{
+ treeStore: treeStore,
+ }
+}
+
+func OpenChangeset(treeStore *TreeStore, dir string) (*Changeset, error) {
+ files, err := OpenChangesetFiles(dir)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open changeset files: %w", err)
+ }
+ cs := NewChangeset(treeStore)
+ err = cs.InitOwned(files)
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize changeset: %w", err)
+ }
+ return cs, nil
+}
+
+func (cr *Changeset) InitOwned(files *ChangesetFiles) error {
+ err := cr.InitShared(files)
+ if err != nil {
+ return err
+ }
+ cr.files = files
+ return nil
+}
+
+func (cr *Changeset) InitShared(files *ChangesetFiles) error {
+ var err error
+
+ cr.kvLog, err = NewKVLog(files.kvlogFile)
+ if err != nil {
+ return fmt.Errorf("failed to open KV data store: %w", err)
+ }
+
+ cr.leavesData, err = NewNodeReader[LeafLayout](files.leavesFile)
+ if err != nil {
+ return fmt.Errorf("failed to open leaves data file: %w", err)
+ }
+
+ cr.branchesData, err = NewNodeReader[BranchLayout](files.branchesFile)
+ if err != nil {
+ return fmt.Errorf("failed to open branches data file: %w", err)
+ }
+
+ cr.versionsData, err = NewStructReader[VersionInfo](files.versionsFile)
+ if err != nil {
+ return fmt.Errorf("failed to open versions data file: %w", err)
+ }
+
+ cr.orphanWriter = NewOrphanWriter(files.orphansFile)
+
+ cr.info = files.info
+
+ return nil
+}
+
+func (cr *Changeset) getVersionInfo(version uint32) (*VersionInfo, error) {
+ info := cr.info
+ if version < info.StartVersion || version >= info.StartVersion+uint32(cr.versionsData.Count()) {
+ return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, info.StartVersion, info.StartVersion+uint32(cr.versionsData.Count())-1)
+ }
+ return cr.versionsData.UnsafeItem(version - info.StartVersion), nil
+}
+
+func (cr *Changeset) ReadK(nodeId NodeID, offset uint32) (key []byte, err error) {
+ if cr.evicted.Load() {
+ return cr.treeStore.ReadK(nodeId)
+ }
+ cr.Pin()
+ defer cr.Unpin()
+
+ k, err := cr.kvLog.UnsafeReadK(offset)
+ if err != nil {
+ return nil, err
+ }
+ copyKey := make([]byte, len(k))
+ copy(copyKey, k)
+ return copyKey, nil
+}
+
+func (cr *Changeset) ReadKV(nodeId NodeID, offset uint32) (key, value []byte, err error) {
+ if cr.evicted.Load() {
+ return cr.treeStore.ReadKV(nodeId)
+ }
+ cr.Pin()
+ defer cr.Unpin()
+
+ k, v, err := cr.kvLog.UnsafeReadKV(offset)
+ if err != nil {
+ return nil, nil, err
+ }
+ copyKey := make([]byte, len(k))
+ copy(copyKey, k)
+ copyValue := make([]byte, len(v))
+ copy(copyValue, v)
+ return copyKey, copyValue, nil
+}
+
+func (cr *Changeset) ReadV(nodeId NodeID, offset uint32) (value []byte, err error) {
+ if cr.evicted.Load() {
+ return cr.treeStore.ReadV(nodeId)
+ }
+ cr.Pin()
+ defer cr.Unpin()
+
+ _, v, err := cr.kvLog.UnsafeReadKV(offset)
+ if err != nil {
+ return nil, err
+ }
+ copyValue := make([]byte, len(v))
+ copy(copyValue, v)
+ return copyValue, nil
+}
+
+func (cr *Changeset) ResolveLeaf(nodeId NodeID, fileIdx uint32) (LeafLayout, error) {
+ if cr.evicted.Load() {
+ return cr.treeStore.ResolveLeaf(nodeId)
+ }
+ cr.Pin()
+ defer cr.Unpin()
+
+ if fileIdx == 0 {
+ version := uint32(nodeId.Version())
+ vi, err := cr.getVersionInfo(version)
+ if err != nil {
+ return LeafLayout{}, err
+ }
+ leaf, err := cr.leavesData.FindByID(nodeId, &vi.Leaves)
+ if err != nil {
+ return LeafLayout{}, err
+ }
+ return *leaf, nil
+ } else {
+ fileIdx-- // convert to 0-based index
+ return *cr.leavesData.UnsafeItem(fileIdx), nil
+ }
+}
+
+func (cr *Changeset) ResolveBranch(nodeId NodeID, fileIdx uint32) (BranchLayout, error) {
+ if cr.evicted.Load() {
+ return cr.treeStore.ResolveBranch(nodeId)
+ }
+
+ layout, _, err := cr.resolveBranchWithIdx(nodeId, fileIdx)
+ return layout, err
+}
+
+func (cr *Changeset) resolveBranchWithIdx(nodeId NodeID, fileIdx uint32) (BranchLayout, uint32, error) {
+ cr.Pin()
+ defer cr.Unpin()
+
+ if fileIdx == 0 {
+ version := uint32(nodeId.Version())
+ vi, err := cr.getVersionInfo(version)
+ if err != nil {
+ return BranchLayout{}, 0, err
+ }
+ branch, err := cr.branchesData.FindByID(nodeId, &vi.Branches)
+ if err != nil {
+ return BranchLayout{}, 0, err
+ }
+ // Compute the actual file index from the pointer
+ itemIdx := uint32((uintptr(unsafe.Pointer(branch)) - uintptr(unsafe.Pointer(&cr.branchesData.items[0]))) / uintptr(cr.branchesData.size))
+ return *branch, itemIdx + 1, nil // +1 to convert back to 1-based
+ } else {
+ itemIdx := fileIdx - 1 // convert to 0-based index
+ return *cr.branchesData.UnsafeItem(itemIdx), fileIdx, nil // return original fileIdx
+ }
+}
+
+func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) {
+ if cr.evicted.Load() {
+ return cr.treeStore.Resolve(nodeId, fileIdx)
+ }
+ cr.Pin()
+ defer cr.Unpin()
+
+ // we don't have a fileIdx, so its probably not in this changeset.
+ if fileIdx == 0 {
+ // load up the changeset for this node
+ cs := cr.treeStore.getChangesetForVersion(uint32(nodeId.Version()))
+ cs.Pin()
+ defer cs.Unpin()
+
+ // get version data
+ version := uint32(nodeId.Version())
+ vi, err := cs.getVersionInfo(version)
+ if err != nil {
+ return nil, err
+ }
+ if nodeId.IsLeaf() {
+ leaf, err := cs.leavesData.FindByID(nodeId, &vi.Leaves)
+ if err != nil {
+ return nil, err
+ }
+ return &LeafPersisted{
+ store: cs,
+ selfIdx: 0,
+ layout: *leaf,
+ }, nil
+ } else {
+ branch, err := cs.branchesData.FindByID(nodeId, &vi.Branches)
+ if err != nil {
+ return nil, err
+ }
+ return &BranchPersisted{
+ store: cs,
+ layout: *branch,
+ }, nil
+ }
+ } else {
+ // since we have the fileIdx, we know it's in this changeset.
+ // we can just directly index in this changeset's leaf/branch data.
+ if nodeId.IsLeaf() {
+ itemIdx := fileIdx - 1
+ leafLayout := *cr.leavesData.UnsafeItem(itemIdx)
+ return &LeafPersisted{layout: leafLayout, store: cr}, nil
+ } else {
+ itemIdx := fileIdx - 1
+ branchLayout := *cr.branchesData.UnsafeItem(itemIdx)
+ return &BranchPersisted{
+ layout: branchLayout,
+ store: cr,
+ }, nil
+ }
+ }
+}
+
+var ErrDisposed = errors.New("changeset disposed")
+
+func (cr *Changeset) MarkOrphan(version uint32, nodeId NodeID) error {
+ err := cr.orphanWriter.WriteOrphan(version, nodeId)
+ if err != nil {
+ return fmt.Errorf("failed to write orphan node: %w", err)
+ }
+
+ info := cr.info
+ if nodeId.IsLeaf() {
+ info.LeafOrphans++
+ info.LeafOrphanVersionTotal += uint64(version)
+ } else {
+ info.BranchOrphans++
+ info.BranchOrphanVersionTotal += uint64(version)
+ }
+
+ return nil
+}
+
+func (cr *Changeset) ReadyToCompact(orphanPercentTarget float64, orphanAgeTarget uint32) bool {
+ info := cr.info
+ leafOrphanCount := info.LeafOrphans
+ if leafOrphanCount > 0 {
+ leafOrphanPercent := float64(leafOrphanCount) / float64(cr.leavesData.Count())
+ leafOrphanAge := uint32(info.LeafOrphanVersionTotal / uint64(info.LeafOrphans))
+
+ if leafOrphanPercent >= orphanPercentTarget && leafOrphanAge <= orphanAgeTarget {
+ return true
+ }
+ }
+
+ branchOrphanCount := info.BranchOrphans
+ if branchOrphanCount > 0 {
+ branchOrphanPercent := float64(branchOrphanCount) / float64(cr.branchesData.Count())
+ branchOrphanAge := uint32(info.BranchOrphanVersionTotal / uint64(info.BranchOrphans))
+ if branchOrphanPercent >= orphanPercentTarget && branchOrphanAge <= orphanAgeTarget {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (cr *Changeset) Close() error {
+ errs := []error{
+ cr.kvLog.Close(),
+ cr.leavesData.Close(),
+ cr.branchesData.Close(),
+ cr.versionsData.Close(),
+ cr.orphanWriter.Flush(),
+ }
+ if cr.files != nil {
+ errs = append(errs, cr.files.Close())
+ }
+ return errors.Join(errs...)
+}
+
+func (cr *Changeset) Pin() {
+ cr.refCount.Add(1)
+}
+
+func (cr *Changeset) Unpin() {
+ cr.refCount.Add(-1)
+}
+
+func (cr *Changeset) Evict() {
+ cr.evicted.Store(true)
+}
+
+func (cr *Changeset) TryDispose() bool {
+ if cr.disposed.Load() {
+ return true
+ }
+ if cr.refCount.Load() <= 0 {
+ if cr.disposed.CompareAndSwap(false, true) {
+ _ = cr.Close()
+ cr.versionsData = nil
+ cr.branchesData = nil
+ cr.leavesData = nil
+ cr.kvLog = nil
+ // DO NOT set treeStore to nil, as deposed changesets should still forward calls to the main tree store
+ // DO NOT set files to nil, as we might need to delete them later
+ return true
+ }
+ }
+ return false
+}
+
+func (cr *Changeset) TotalBytes() int {
+ return cr.leavesData.TotalBytes() +
+ cr.branchesData.TotalBytes() +
+ cr.kvLog.TotalBytes() +
+ cr.versionsData.TotalBytes()
+}
+
+func (cr *Changeset) HasOrphans() bool {
+ info := cr.info
+ return info.LeafOrphans > 0 || info.BranchOrphans > 0
+}
+
+func (cr *Changeset) ResolveRoot(version uint32) (*NodePointer, error) {
+ startVersion := cr.info.StartVersion
+ endVersion := startVersion + uint32(cr.versionsData.Count()) - 1
+ if version < startVersion || version > endVersion {
+ return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, startVersion, endVersion)
+ }
+ vi, err := cr.getVersionInfo(version)
+ if err != nil {
+ return nil, err
+ }
+ if vi.RootID == 0 {
+ // empty tree
+ return nil, nil
+ }
+ return &NodePointer{
+ id: vi.RootID,
+ store: cr,
+ }, nil
+}
diff --git a/iavlx/changeset_files.go b/iavlx/changeset_files.go
new file mode 100644
index 000000000000..14262f0a6ac1
--- /dev/null
+++ b/iavlx/changeset_files.go
@@ -0,0 +1,290 @@
+package iavlx
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+)
+
+type ChangesetFiles struct {
+ dir string
+ treeDir string
+ startVersion uint32
+ compactedAt uint32
+
+ kvlogFile *os.File
+ kvlogPath string
+ branchesFile *os.File
+ leavesFile *os.File
+ versionsFile *os.File
+ orphansFile *os.File
+ infoFile *os.File
+ info *ChangesetInfo
+
+ closed bool
+}
+
+func CreateChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogPath string) (*ChangesetFiles, error) {
+ // ensure absolute path
+ var err error
+ treeDir, err = filepath.Abs(treeDir)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get absolute path for %s: %w", treeDir, err)
+ }
+
+ dirName := fmt.Sprintf("%d", startVersion)
+ if compactedAt > 0 {
+ dirName = fmt.Sprintf("%d.%d", startVersion, compactedAt)
+ }
+ dir := filepath.Join(treeDir, dirName)
+
+ err = os.MkdirAll(dir, 0o755)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create changeset dir: %w", err)
+ }
+
+ // create pending marker file for compacted changesets
+ if compactedAt > 0 {
+ err := os.WriteFile(filepath.Join(dir, "pending"), []byte{}, 0o644)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create pending marker file for compacted changeset: %w", err)
+ }
+ }
+
+ localKVLogPath := filepath.Join(dir, "kv.log")
+ if kvlogPath == "" {
+ // For original (non-compacted) changesets, normalize the path by evaluating
+ // symlinks in the directory path to ensure consistent comparisons later.
+ // This handles platform differences like /var vs /private/var on macOS.
+ normalizedDir, err := filepath.EvalSymlinks(dir)
+ if err != nil {
+ return nil, fmt.Errorf("failed to eval directory path: %w", err)
+ }
+ kvlogPath = filepath.Join(normalizedDir, "kv.log")
+ } else {
+ // create symlink to kvlog so that it can be reopened later
+ err := os.Symlink(kvlogPath, localKVLogPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create kvlog symlink: %w", err)
+ }
+ kvlogPath, err = filepath.EvalSymlinks(localKVLogPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to eval kvlog symlink: %w", err)
+ }
+ }
+
+ cr := &ChangesetFiles{
+ dir: dir,
+ treeDir: treeDir,
+ startVersion: startVersion,
+ compactedAt: compactedAt,
+ kvlogPath: kvlogPath,
+ }
+
+ err = cr.open(os.O_RDWR | os.O_CREATE | os.O_APPEND)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open changeset files: %w", err)
+ }
+ return cr, nil
+}
+
+func OpenChangesetFiles(dirName string) (*ChangesetFiles, error) {
+ startVersion, compactedAt, valid := ParseChangesetDirName(filepath.Base(dirName))
+ if !valid {
+ return nil, fmt.Errorf("invalid changeset dir name: %s", dirName)
+ }
+
+ dir, err := filepath.Abs(dirName)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get absolute path for %s: %w", dirName, err)
+ }
+
+ treeDir := filepath.Dir(dir)
+
+ localKVLogPath := filepath.Join(dir, "kv.log")
+ kvlogPath, err := filepath.EvalSymlinks(localKVLogPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to eval kvlog symlink: %w", err)
+ }
+
+ cr := &ChangesetFiles{
+ dir: dir,
+ treeDir: treeDir,
+ startVersion: uint32(startVersion),
+ compactedAt: uint32(compactedAt),
+ kvlogPath: kvlogPath,
+ }
+
+ err = cr.open(os.O_RDWR)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open changeset files: %w", err)
+ }
+
+ return cr, nil
+}
+
+func (cr *ChangesetFiles) open(mode int) error {
+ var err error
+ leavesPath := filepath.Join(cr.dir, "leaves.dat")
+
+ cr.kvlogFile, err = os.OpenFile(cr.kvlogPath, mode, 0o644)
+ if err != nil {
+ return fmt.Errorf("failed to create KV log file: %w", err)
+ }
+
+ cr.leavesFile, err = os.OpenFile(leavesPath, mode, 0o644)
+ if err != nil {
+ return fmt.Errorf("failed to create leaves data file: %w", err)
+ }
+
+ branchesPath := filepath.Join(cr.dir, "branches.dat")
+ cr.branchesFile, err = os.OpenFile(branchesPath, mode, 0o644)
+ if err != nil {
+ return fmt.Errorf("failed to create branches data file: %w", err)
+ }
+
+ versionsPath := filepath.Join(cr.dir, "versions.dat")
+ cr.versionsFile, err = os.OpenFile(versionsPath, mode, 0o644)
+ if err != nil {
+ return fmt.Errorf("failed to create versions data file: %w", err)
+ }
+
+ orphansPath := filepath.Join(cr.dir, "orphans.dat")
+ cr.orphansFile, err = os.OpenFile(orphansPath, mode, 0o644)
+ if err != nil {
+ return fmt.Errorf("failed to create orphans data file: %w", err)
+ }
+
+ infoPath := filepath.Join(cr.dir, "info.dat")
+ cr.infoFile, err = os.OpenFile(infoPath, mode, 0o644)
+ if err != nil {
+ return fmt.Errorf("failed to create changeset info file: %w", err)
+ }
+
+ cr.info, err = ReadChangesetInfo(cr.infoFile)
+ if err != nil {
+ return fmt.Errorf("failed to read changeset info: %w", err)
+ }
+
+ return nil
+}
+
+func ParseChangesetDirName(dirName string) (startVersion, compactedAt uint64, valid bool) {
+ var err error
+ // if no dot, it's an original changeset
+ if !strings.Contains(dirName, ".") {
+ startVersion, err = strconv.ParseUint(dirName, 10, 64)
+ if err != nil {
+ return 0, 0, false
+ }
+ return startVersion, 0, true
+ } else {
+ parts := strings.Split(dirName, ".")
+ if len(parts) != 2 {
+ return 0, 0, false
+ }
+ startVersion, err = strconv.ParseUint(parts[0], 10, 64)
+ if err != nil {
+ return 0, 0, false
+ }
+ compactedAt, err = strconv.ParseUint(parts[1], 10, 64)
+ if err != nil {
+ return 0, 0, false
+ }
+ return startVersion, compactedAt, true
+ }
+}
+
+func (cr *ChangesetFiles) TreeDir() string {
+ return cr.treeDir
+}
+
+func (cr *ChangesetFiles) KVLogPath() string {
+ return cr.kvlogFile.Name()
+}
+
+func (cr *ChangesetFiles) StartVersion() uint32 {
+ return cr.startVersion
+}
+
+func (cr *ChangesetFiles) CompactedAtVersion() uint32 {
+ return cr.compactedAt
+}
+
+func (cr *ChangesetFiles) Info() *ChangesetInfo {
+ return cr.info
+}
+
+func (cr *ChangesetFiles) RewriteInfo() error {
+ return RewriteChangesetInfo(cr.infoFile, cr.info)
+}
+
+func IsChangesetReady(dir string) (bool, error) {
+ pendingPath := filepath.Join(dir, "pending")
+ _, err := os.Stat(pendingPath)
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return true, nil
+ }
+ return false, fmt.Errorf("failed to stat pending marker file: %w", err)
+ }
+ return false, nil
+}
+
+func (cr *ChangesetFiles) MarkReady() error {
+ pendingPath := filepath.Join(cr.dir, "pending")
+ err := os.Remove(pendingPath)
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return fmt.Errorf("failed to remove pending marker file: %w", err)
+ }
+ return nil
+}
+
+type ChangesetDeleteArgs struct {
+ SaveKVLogPath string
+}
+
+func (cr *ChangesetFiles) Close() error {
+ if cr.closed {
+ return nil
+ }
+
+ cr.closed = true
+ err := errors.Join(
+ cr.RewriteInfo(),
+ cr.kvlogFile.Close(),
+ cr.branchesFile.Close(),
+ cr.leavesFile.Close(),
+ cr.versionsFile.Close(),
+ cr.orphansFile.Close(),
+ cr.infoFile.Close(),
+ )
+ cr.info = nil
+ return err
+}
+
+func (cr *ChangesetFiles) DeleteFiles(args ChangesetDeleteArgs) error {
+ errs := []error{
+ os.Remove(cr.infoFile.Name()),
+ os.Remove(cr.leavesFile.Name()),
+ os.Remove(cr.branchesFile.Name()),
+ os.Remove(cr.versionsFile.Name()),
+ os.Remove(cr.orphansFile.Name()),
+ }
+
+ localKVLogPath := filepath.Join(cr.dir, "kv.log")
+ if cr.kvlogPath != args.SaveKVLogPath {
+ // delete the local kv.log (which might be a symlink)
+ errs = append(errs, os.Remove(localKVLogPath))
+ }
+ err := errors.Join(errs...)
+ if err != nil {
+ return fmt.Errorf("failed to delete changeset files: %w", err)
+ }
+ // delete dir if empty
+ _ = os.Remove(cr.dir)
+ return nil
+}
diff --git a/iavlx/changeset_info.go b/iavlx/changeset_info.go
new file mode 100644
index 000000000000..34b21040b68e
--- /dev/null
+++ b/iavlx/changeset_info.go
@@ -0,0 +1,61 @@
+package iavlx
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "unsafe"
+)
+
+type ChangesetInfo struct {
+ StartVersion uint32
+ EndVersion uint32
+ LeafOrphans uint32
+ BranchOrphans uint32
+ LeafOrphanVersionTotal uint64
+ BranchOrphanVersionTotal uint64
+}
+
+// RewriteChangesetInfo truncates and rewrites the info file with the given changeset info.
+func RewriteChangesetInfo(file *os.File, info *ChangesetInfo) error {
+ if err := file.Truncate(0); err != nil {
+ return fmt.Errorf("failed to truncate info file: %w", err)
+ }
+ if _, err := file.Seek(0, 0); err != nil {
+ return fmt.Errorf("failed to seek info file: %w", err)
+ }
+
+ size := int(unsafe.Sizeof(*info))
+ data := unsafe.Slice((*byte)(unsafe.Pointer(info)), size)
+ if _, err := file.Write(data); err != nil {
+ return fmt.Errorf("failed to write changeset info: %w", err)
+ }
+
+ return nil
+}
+
+// ReadChangesetInfo reads changeset info from a file. Returns an empty default struct if file is zero length.
+func ReadChangesetInfo(file *os.File) (*ChangesetInfo, error) {
+ stat, err := file.Stat()
+ if err != nil {
+ return nil, fmt.Errorf("failed to stat info file: %w", err)
+ }
+
+ if stat.Size() == 0 {
+ return &ChangesetInfo{}, nil
+ }
+
+ var info ChangesetInfo
+ size := int(unsafe.Sizeof(info))
+
+ if stat.Size() != int64(size) {
+ return nil, fmt.Errorf("info file has unexpected size: %d, expected %d", stat.Size(), size)
+ }
+
+ buf := make([]byte, size)
+ if _, err := io.ReadFull(file, buf); err != nil {
+ return nil, fmt.Errorf("failed to read changeset info: %w", err)
+ }
+
+ return (*ChangesetInfo)(unsafe.Pointer(&buf[0])), nil
+}
diff --git a/iavlx/changeset_writer.go b/iavlx/changeset_writer.go
new file mode 100644
index 000000000000..a08f1410e0d1
--- /dev/null
+++ b/iavlx/changeset_writer.go
@@ -0,0 +1,273 @@
+package iavlx
+
+import (
+ "errors"
+ "fmt"
+ "sync/atomic"
+ "unsafe"
+)
+
+type ChangesetWriter struct {
+ stagedVersion uint32
+
+ files *ChangesetFiles
+ needsSync atomic.Bool
+
+ kvlog *KVLogWriter
+ branchesData *StructWriter[BranchLayout]
+ leavesData *StructWriter[LeafLayout]
+ versionsData *StructWriter[VersionInfo]
+
+ reader *Changeset
+
+ keyCache map[string]uint32
+}
+
+func NewChangesetWriter(treeDir string, startVersion uint32, treeStore *TreeStore) (*ChangesetWriter, error) {
+ files, err := CreateChangesetFiles(treeDir, startVersion, 0, "")
+ if err != nil {
+ return nil, fmt.Errorf("failed to open changeset files: %w", err)
+ }
+
+ cs := &ChangesetWriter{
+ stagedVersion: startVersion,
+ files: files,
+ kvlog: NewKVDataWriter(files.kvlogFile),
+ branchesData: NewStructWriter[BranchLayout](files.branchesFile),
+ leavesData: NewStructWriter[LeafLayout](files.leavesFile),
+ versionsData: NewStructWriter[VersionInfo](files.versionsFile),
+ reader: NewChangeset(treeStore),
+ keyCache: make(map[string]uint32),
+ }
+ return cs, nil
+}
+
+func (cs *ChangesetWriter) WriteWALUpdates(updates []KVUpdate) error {
+ return cs.kvlog.WriteUpdates(updates)
+}
+
+func (cs *ChangesetWriter) WriteWALCommit(version uint32) error {
+ return cs.kvlog.WriteCommit(version)
+}
+
+func (cs *ChangesetWriter) SaveRoot(root *NodePointer, version, totalLeaves, totalBranches uint32) error {
+ cs.needsSync.Store(true)
+
+ if version != cs.stagedVersion {
+ return fmt.Errorf("version mismatch: expected %d, got %d", cs.stagedVersion, version)
+ }
+
+ var versionInfo VersionInfo
+ versionInfo.Branches.StartOffset = uint32(cs.branchesData.Count())
+ versionInfo.Leaves.StartOffset = uint32(cs.leavesData.Count())
+ if totalBranches > 0 {
+ versionInfo.Branches.StartIndex = 1
+ versionInfo.Branches.Count = totalBranches
+ versionInfo.Branches.EndIndex = totalBranches
+ }
+ if totalLeaves > 0 {
+ versionInfo.Leaves.StartIndex = 1
+ versionInfo.Leaves.Count = totalLeaves
+ versionInfo.Leaves.EndIndex = totalLeaves
+ }
+
+ if root != nil {
+ err := cs.writeNode(root)
+ if err != nil {
+ return err
+ }
+
+ versionInfo.RootID = root.id
+ }
+
+ // commit version info
+ err := cs.versionsData.Append(&versionInfo)
+ if err != nil {
+ return fmt.Errorf("failed to write version info: %w", err)
+ }
+
+ // Set start version on first successful save
+ info := cs.files.info
+ if info.StartVersion == 0 {
+ info.StartVersion = version
+ }
+
+ // Always update end version
+ info.EndVersion = version
+
+ cs.stagedVersion++
+
+ return nil
+}
+
+func (cs *ChangesetWriter) CreatedSharedReader() (*Changeset, error) {
+ err := cs.Flush()
+ if err != nil {
+ return nil, fmt.Errorf("failed to flush data before creating shared reader: %w", err)
+ }
+
+ err = cs.reader.InitShared(cs.files)
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize shared changeset reader: %w", err)
+ }
+
+ reader := cs.reader
+ cs.reader = NewChangeset(reader.treeStore)
+ return reader, nil
+}
+
+func (cs *ChangesetWriter) Flush() error {
+ return errors.Join(
+ cs.files.RewriteInfo(),
+ cs.leavesData.Flush(),
+ cs.branchesData.Flush(),
+ cs.kvlog.Flush(),
+ cs.versionsData.Flush(),
+ )
+}
+
+func (cs *ChangesetWriter) writeNode(np *NodePointer) error {
+ memNode := np.mem.Load()
+ if memNode == nil {
+ return nil // already persisted
+ }
+ if memNode.version != cs.stagedVersion {
+ return nil // not part of this version
+ }
+ if memNode.IsLeaf() {
+ return cs.writeLeaf(np, memNode)
+ } else {
+ return cs.writeBranch(np, memNode)
+ }
+}
+
+func (cs *ChangesetWriter) writeBranch(np *NodePointer, node *MemNode) error {
+ // recursively write children in post-order traversal
+ err := cs.writeNode(node.left)
+ if err != nil {
+ return err
+ }
+ err = cs.writeNode(node.right)
+ if err != nil {
+ return err
+ }
+
+ // TODO cache key offset in memory to avoid duplicate writes
+ keyOffset, ok := cs.keyCache[unsafeBytesToString(node.key)]
+ if !ok {
+ var err error
+ keyOffset, err = cs.kvlog.WriteK(node.key)
+ if err != nil {
+ return fmt.Errorf("failed to write key data: %w", err)
+ }
+ }
+
+ leftVersion := node.left.id.Version()
+ rightVersion := node.right.id.Version()
+
+ var leftOffset uint32
+ var rightOffset uint32
+
+ // If the child node is in the same changeset, store its 1-based file offset.
+ // fileIdx is already 1-based (set to Count() after append), and 0 means no offset.
+ if leftVersion >= uint64(cs.StartVersion()) {
+ leftOffset = node.left.fileIdx
+ }
+ if rightVersion >= uint64(cs.StartVersion()) {
+ rightOffset = node.right.fileIdx
+ }
+
+ layout := BranchLayout{
+ Id: np.id,
+ Left: node.left.id,
+ Right: node.right.id,
+ LeftOffset: leftOffset,
+ RightOffset: rightOffset,
+ KeyOffset: keyOffset,
+ Height: node.height,
+ Size: uint32(node.size), // TODO check overflow
+ }
+ copy(layout.Hash[:], node.hash) // TODO check length
+
+ err = cs.branchesData.Append(&layout) // TODO check error
+ if err != nil {
+ return fmt.Errorf("failed to write branch node: %w", err)
+ }
+
+ np.fileIdx = uint32(cs.branchesData.Count())
+ np.store = cs.reader
+
+ return nil
+}
+
+func (cs *ChangesetWriter) writeLeaf(np *NodePointer, node *MemNode) error {
+ keyOffset := node.keyOffset
+ if keyOffset == 0 {
+ var err error
+ keyOffset, err = cs.kvlog.WriteKV(node.key, node.value)
+ if err != nil {
+ return fmt.Errorf("failed to write key-value data: %w", err)
+ }
+ }
+
+ layout := LeafLayout{
+ Id: np.id,
+ KeyOffset: keyOffset,
+ }
+ copy(layout.Hash[:], node.hash) // TODO check length
+
+ err := cs.leavesData.Append(&layout)
+ if err != nil {
+ return fmt.Errorf("failed to write leaf node: %w", err)
+ }
+
+ np.fileIdx = uint32(cs.leavesData.Count())
+ np.store = cs.reader
+
+ cs.keyCache[unsafeBytesToString(node.key)] = keyOffset
+
+ return nil
+}
+
+func (cs *ChangesetWriter) TotalBytes() int {
+ return cs.leavesData.Size() +
+ cs.branchesData.Size() +
+ cs.versionsData.Size() +
+ cs.kvlog.Size()
+}
+
+func (cs *ChangesetWriter) Seal() (*Changeset, error) {
+ err := cs.Flush()
+ if err != nil {
+ return nil, fmt.Errorf("failed to flush changeset data: %w", err)
+ }
+
+ err = cs.reader.InitOwned(cs.files)
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize owned changeset reader: %w", err)
+ }
+ cs.leavesData = nil
+ cs.branchesData = nil
+ cs.versionsData = nil
+ cs.kvlog = nil
+ cs.keyCache = nil
+ reader := cs.reader
+ cs.reader = nil
+
+ return reader, nil
+}
+
+func (cs *ChangesetWriter) StartVersion() uint32 {
+ return cs.files.StartVersion()
+}
+
+func (cs *ChangesetWriter) SyncWAL() error {
+ if !cs.needsSync.CompareAndSwap(true, false) {
+ return nil
+ }
+ return cs.files.kvlogFile.Sync()
+}
+
+func unsafeBytesToString(b []byte) string {
+ return unsafe.String(unsafe.SliceData(b), len(b))
+}
diff --git a/iavlx/cleanup.go b/iavlx/cleanup.go
new file mode 100644
index 000000000000..996e1f35dae1
--- /dev/null
+++ b/iavlx/cleanup.go
@@ -0,0 +1,436 @@
+package iavlx
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "log/slog"
+ "sync"
+ "time"
+
+ "go.opentelemetry.io/otel"
+)
+
+var (
+ tracer = otel.Tracer("iavlx")
+)
+
+type cleanupProc struct {
+ *TreeStore
+ closeCleanupProc chan struct{}
+ cleanupProcDone chan struct{}
+
+ // Split orphan queues based on whether versions are readable
+ orphanWriteQueue []markOrphansReq // For versions <= savedVersion (can process immediately)
+ stagedOrphanQueue []markOrphansReq // For versions > savedVersion (need to wait)
+ orphanQueueLock sync.Mutex
+
+ toDelete map[*Changeset]ChangesetDeleteArgs
+ activeCompactor *Compactor
+ beingCompacted []compactionEntry
+
+ // Disposal queue for evicted changesets awaiting refcount=0
+ disposalQueue sync.Map // *Changeset -> struct{}
+}
+
+type compactionEntry struct {
+ entry *changesetEntry
+ cs *Changeset
+}
+
+func newCleanupProc(treeStore *TreeStore) *cleanupProc {
+ cp := &cleanupProc{
+ TreeStore: treeStore,
+ closeCleanupProc: make(chan struct{}),
+ cleanupProcDone: make(chan struct{}),
+ toDelete: make(map[*Changeset]ChangesetDeleteArgs),
+ }
+ go cp.run()
+ return cp
+}
+
+func (cp *cleanupProc) run() {
+ ctx, span := tracer.Start(context.Background(), "cleanupProc")
+ defer span.End()
+ // before we shutdown save any pending orphans
+ defer func() {
+ err := cp.doMarkOrphans()
+ if err != nil {
+ cp.logger.Error("failed to mark orphans at shutdown", "error", err)
+ }
+ }()
+ defer close(cp.cleanupProcDone)
+
+ minCompactorInterval := time.Second * time.Duration(cp.opts.MinCompactionSeconds)
+ var lastCompactorStart time.Time
+
+ for {
+ sleepTime := time.Duration(0)
+ if time.Since(lastCompactorStart) < minCompactorInterval {
+ sleepTime = minCompactorInterval - time.Since(lastCompactorStart)
+ }
+ select {
+ case <-cp.closeCleanupProc:
+ return
+ case <-time.After(sleepTime):
+ }
+
+ lastCompactorStart = time.Now()
+
+ // process any pending orphans at the start of each cycle
+ err := cp.doMarkOrphans()
+ if err != nil {
+ cp.logger.Error("failed to mark orphans at start of cycle", "error", err)
+ }
+
+ // collect current entries
+ cp.changesetsMapLock.RLock()
+ var entries []*changesetEntry
+ cp.changesets.Scan(func(version uint32, entry *changesetEntry) bool {
+ entries = append(entries, entry)
+ return true
+ })
+ cp.changesetsMapLock.RUnlock()
+
+ for i := 0; i < len(entries); i++ {
+ entry := entries[i]
+ var nextEntry *changesetEntry
+ if i+1 < len(entries) {
+ nextEntry = entries[i+1]
+ }
+ err := cp.processEntry(ctx, entry, nextEntry)
+ if err != nil {
+ cp.logger.Error("failed to process changeset entry", "error", err)
+ // on error, clean up any failed compaction and stop processing further entries this round
+ cp.cleanupFailedCompaction()
+ break
+ }
+ }
+ if cp.activeCompactor != nil {
+ err := cp.sealActiveCompactor()
+ if err != nil {
+ cp.logger.Error("failed to seal active compactor", "error", err)
+ }
+ }
+
+ cp.processToDelete()
+ cp.processDisposalQueue()
+ }
+}
+
+func (cp *cleanupProc) markOrphans(version uint32, nodeIds [][]NodeID) {
+ req := markOrphansReq{
+ version: version,
+ orphans: nodeIds,
+ }
+
+ cp.orphanQueueLock.Lock()
+ defer cp.orphanQueueLock.Unlock()
+
+ cp.orphanWriteQueue = append(cp.orphanWriteQueue, req)
+}
+
+// doMarkOrphans must only be called from the cleanupProc
+func (cp *cleanupProc) doMarkOrphans() error {
+ var orphanQueue []markOrphansReq
+ cp.orphanQueueLock.Lock()
+ orphanQueue, cp.orphanWriteQueue = cp.orphanWriteQueue, nil
+ cp.orphanQueueLock.Unlock()
+
+ orphanQueue = append(orphanQueue, cp.stagedOrphanQueue...)
+
+ savedVersion := cp.savedVersion.Load()
+ var newStagedOrphans []markOrphansReq
+
+ for _, req := range orphanQueue {
+ for _, nodeSet := range req.orphans {
+ var stagedNodes []NodeID
+ for _, nodeId := range nodeSet {
+ nodeVersion := uint32(nodeId.Version())
+
+ // Route to staged queue if version not yet readable
+ if nodeVersion > savedVersion {
+ stagedNodes = append(stagedNodes, nodeId)
+ continue
+ }
+
+ ce := cp.getChangesetEntryForVersion(nodeVersion)
+ if ce == nil {
+ return fmt.Errorf("no changeset found for version %d", nodeVersion)
+ }
+ // this somewhat awkward retry loop is needed to handle a race condition where
+ // we have disposed of a changeset between getting the entry and marking the orphan
+ retries := 0
+ for {
+ err := ce.changeset.Load().MarkOrphan(req.version, nodeId)
+ if errors.Is(err, ErrDisposed) {
+ if retries > 3 {
+ return fmt.Errorf("changeset for version %d disposed while marking orphan %s", nodeVersion, nodeId.String())
+ }
+ retries++
+ continue
+ } else if err != nil {
+ return err
+ }
+ break
+ }
+ }
+ // Add any staged nodes back to the staged queue
+ if len(stagedNodes) > 0 {
+ newStagedOrphans = append(newStagedOrphans, markOrphansReq{
+ version: req.version,
+ orphans: [][]NodeID{stagedNodes},
+ })
+ }
+ }
+ }
+
+ cp.stagedOrphanQueue = newStagedOrphans
+
+ return nil
+}
+
+func (cp *cleanupProc) processEntry(ctx context.Context, entry, nextEntry *changesetEntry) error {
+ ctx, span := tracer.Start(ctx, "cleanupProc.processEntry")
+ defer span.End()
+
+ cs := entry.changeset.Load()
+
+ if cs.files == nil {
+ // skipping incomplete changeset which is still open for writing
+ return nil
+ }
+
+ // safety check - skip if evicted or disposed
+ if cs.evicted.Load() || cs.disposed.Load() {
+ return fmt.Errorf("evicted/disposed changeset: %s found in queue", cs.files.dir)
+ }
+
+ if cp.opts.DisableCompaction {
+ return nil
+ }
+
+ // skip if still pending sync
+ if cs.needsSync.Load() {
+ return nil
+ }
+
+ if cp.activeCompactor != nil {
+ if cp.opts.CompactWAL &&
+ cs.TotalBytes()+cp.activeCompactor.TotalBytes() <= int(cp.opts.GetCompactionMaxTarget()) {
+ // add to active compactor
+ slog.DebugContext(ctx, "joining changeset to active compactor", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir,
+ "newDir", cp.activeCompactor.files.dir)
+ err := cp.activeCompactor.AddChangeset(cs)
+ if err != nil {
+ return fmt.Errorf("failed to add changeset to active compactor: %w", err)
+ }
+ cp.beingCompacted = append(cp.beingCompacted, compactionEntry{entry: entry, cs: cs})
+ return nil
+ } else {
+ err := cp.sealActiveCompactor()
+ if err != nil {
+ cp.cleanupFailedCompaction()
+ return fmt.Errorf("failed to seal active compactor: %w", err)
+ }
+ }
+ }
+
+ // mark any pending orphans here when we don't have an active compactor
+ err := cp.doMarkOrphans()
+ if err != nil {
+ cp.logger.Error("failed to mark orphans", "error", err)
+ }
+
+ // check if other triggers apply for a new compaction
+ savedVersion := cp.savedVersion.Load()
+ retainVersions := cp.opts.RetainVersions
+ retentionWindowBottom := savedVersion - retainVersions
+ if retainVersions == 0 {
+ // retain everything
+ retentionWindowBottom = 0
+ }
+
+ compactOrphanAge := cp.opts.GetCompactionOrphanAge()
+ compactOrphanThreshold := cp.opts.GetCompactionOrphanRatio()
+
+ // Age target relative to bottom of retention window
+ ageTarget := retentionWindowBottom - compactOrphanAge
+
+ // Check orphan-based trigger
+ shouldCompact := cs.ReadyToCompact(compactOrphanThreshold, ageTarget)
+ if !shouldCompact {
+ lastCompactedAt := cs.files.CompactedAtVersion()
+ if savedVersion-lastCompactedAt >= cp.opts.GetCompactAfterVersions() {
+ shouldCompact = cs.HasOrphans()
+ }
+ }
+
+ // Check size-based joining trigger
+ maxSize := cp.opts.GetCompactionMaxTarget()
+
+ canJoin := false
+ if !shouldCompact && cp.opts.CompactWAL && nextEntry != nil {
+ nextCs := nextEntry.changeset.Load()
+ if nextCs.files != nil && // we can't compact a changeset that's still being written
+ nextCs.info.StartVersion == cs.info.EndVersion+1 {
+ if uint64(cs.TotalBytes())+uint64(nextCs.TotalBytes()) <= maxSize {
+ canJoin = true
+ }
+ }
+ }
+
+ if !shouldCompact && !canJoin {
+ return nil
+ }
+
+ retainVersion := retentionWindowBottom
+ retainCriteria := func(createVersion, orphanVersion uint32) bool {
+ // orphanVersion should be non-zero
+ if orphanVersion >= retainVersion {
+ // keep the orphan if it's in the retain window
+ return true
+ } else {
+ // otherwise, we can remove it
+ return false
+ }
+ }
+
+ slog.Info("compacting changeset", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir)
+
+ cp.activeCompactor, err = NewCompacter(ctx, cs, CompactOptions{
+ RetainCriteria: retainCriteria,
+ CompactWAL: cp.opts.CompactWAL,
+ CompactedAt: savedVersion,
+ }, cp.TreeStore)
+ if err != nil {
+ return fmt.Errorf("failed to create compactor: %w", err)
+ }
+ cp.beingCompacted = []compactionEntry{{entry: entry, cs: cs}}
+ return nil
+}
+
+func (cp *cleanupProc) sealActiveCompactor() error {
+ // seal compactor and finish
+ newCs, err := cp.activeCompactor.Seal()
+ if err != nil {
+ return fmt.Errorf("failed to seal active compactor: %w", err)
+ }
+
+ // update all processed entries to point to new changeset
+ oldSize := uint64(0)
+ for i, procEntry := range cp.beingCompacted {
+ cp.logger.Debug("updating changeset entry to compacted changeset and trying to delete",
+ "old_dir", procEntry.cs.files.dir, "new_dir", newCs.files.dir)
+
+ oldCs := procEntry.cs
+ oldDir := oldCs.files.dir
+ oldSize += uint64(oldCs.TotalBytes())
+
+ if i == 0 {
+ procEntry.entry.changeset.Store(newCs)
+ } else {
+ cp.changesetsMapLock.Lock()
+ cp.changesets.Delete(oldCs.files.StartVersion())
+ cp.changesetsMapLock.Unlock()
+ }
+ oldCs.Evict()
+
+ // try to delete now or schedule for later
+ if !oldCs.TryDispose() {
+ cp.logger.Debug("changeset has active references, scheduling for deletion", "path", oldDir, "refcount", oldCs.refCount.Load())
+ cp.toDelete[oldCs] = ChangesetDeleteArgs{newCs.files.KVLogPath()}
+ } else {
+ cp.logger.Info("changeset disposed, deleting files", "path", oldDir)
+ err = oldCs.files.DeleteFiles(ChangesetDeleteArgs{SaveKVLogPath: newCs.files.KVLogPath()})
+ if err != nil {
+ cp.logger.Error("failed to delete old changeset files", "error", err, "path", oldDir)
+ }
+ }
+ }
+
+ cp.logger.Info("compacted changeset", "dir", newCs.files.dir, "new_size", newCs.TotalBytes(), "old_size", oldSize, "joined", len(cp.beingCompacted))
+
+ // Clear compactor state after successful seal
+ cp.activeCompactor = nil
+ cp.beingCompacted = nil
+ return nil
+}
+
+func (cp *cleanupProc) cleanupFailedCompaction() {
+ // clean up any partial compactor state and remove temporary files
+ if cp.activeCompactor != nil && cp.activeCompactor.files != nil {
+ cp.logger.Warn("cleaning up failed compaction", "dir", cp.activeCompactor.files.dir, "changesets_attempted", len(cp.beingCompacted))
+ err := cp.activeCompactor.Abort()
+ if err != nil {
+ cp.logger.Error("failed to abort active compactor", "error", err)
+ }
+ }
+ cp.activeCompactor = nil
+ cp.beingCompacted = nil
+}
+
+func (cp *cleanupProc) processToDelete() {
+ if len(cp.toDelete) > 0 {
+ cp.logger.Debug("processing delete queue", "size", len(cp.toDelete))
+ }
+
+ for oldCs, args := range cp.toDelete {
+ select {
+ case <-cp.closeCleanupProc:
+ return
+ default:
+ }
+
+ if !oldCs.TryDispose() {
+ cp.logger.Warn("old changeset not disposed, skipping delete", "path", oldCs.files.dir, "refcount", oldCs.refCount.Load())
+ continue
+ }
+
+ cp.logger.Info("deleting old changeset files", "path", oldCs.files.dir)
+ err := oldCs.files.DeleteFiles(args)
+ if err != nil {
+ cp.logger.Error("failed to delete old changeset files", "error", err)
+ }
+ delete(cp.toDelete, oldCs)
+ }
+}
+
+func (cp *cleanupProc) shutdown() {
+ close(cp.closeCleanupProc)
+ <-cp.cleanupProcDone
+}
+
+// addPendingDisposal adds an evicted changeset to the disposal queue
+func (cp *cleanupProc) addPendingDisposal(cs *Changeset) {
+ cp.disposalQueue.Store(cs, struct{}{})
+}
+
+// processDisposalQueue tries to dispose changesets waiting for refcount=0
+func (cp *cleanupProc) processDisposalQueue() {
+ disposalCount := 0
+ cp.disposalQueue.Range(func(key, value interface{}) bool {
+ disposalCount++
+ return true
+ })
+
+ if disposalCount > 0 {
+ cp.logger.Debug("processing disposal queue", "size", disposalCount)
+ }
+
+ cp.disposalQueue.Range(func(key, value interface{}) bool {
+ cs := key.(*Changeset)
+ if cs.TryDispose() {
+ cp.disposalQueue.Delete(cs)
+ cp.logger.Debug("disposed shared changeset from queue")
+ } else {
+ cp.logger.Debug("shared changeset still has references", "refcount", cs.refCount.Load())
+ }
+ return true
+ })
+
+ // Warn if the disposal queue is getting large
+ if disposalCount > 100 {
+ cp.logger.Warn("disposal queue is large", "size", disposalCount)
+ }
+}
diff --git a/iavlx/commit_multi_tree.go b/iavlx/commit_multi_tree.go
new file mode 100644
index 000000000000..a41fbc259de0
--- /dev/null
+++ b/iavlx/commit_multi_tree.go
@@ -0,0 +1,433 @@
+package iavlx
+
+import (
+ "bytes"
+ "fmt"
+ io "io"
+ "os"
+ "path/filepath"
+ "sync"
+
+ dbm "github.com/cosmos/cosmos-db"
+ protoio "github.com/cosmos/gogoproto/io"
+
+ "cosmossdk.io/log"
+
+ "cosmossdk.io/store/mem"
+ "cosmossdk.io/store/metrics"
+ pruningtypes "cosmossdk.io/store/pruning/types"
+ snapshottypes "cosmossdk.io/store/snapshots/types"
+ "cosmossdk.io/store/transient"
+ storetypes "cosmossdk.io/store/types"
+)
+
+type CommitMultiTree struct {
+ dir string
+ opts Options
+ logger log.Logger
+ trees []storetypes.CommitStore // always ordered by tree name
+ treeKeys []storetypes.StoreKey // always ordered by tree name
+ storeTypes []storetypes.StoreType // store types by tree index
+ treesByKey map[storetypes.StoreKey]int // index of the trees by name
+
+ version uint64
+ lastCommitId storetypes.CommitID
+ workingCommitInfo *storetypes.CommitInfo
+ workingHash []byte
+}
+
+// GetObjKVStore returns a mounted ObjKVStore for a given StoreKey.
+func (db *CommitMultiTree) GetObjKVStore(key storetypes.StoreKey) storetypes.ObjKVStore {
+ treeIdx, ok := db.treesByKey[key]
+ if !ok {
+ panic(fmt.Sprintf("tree key not found in treesByKey: %v", key))
+ }
+ s := db.trees[treeIdx]
+ if s == nil {
+ panic(fmt.Sprintf("store does not exist for key: %s", key.Name()))
+ }
+ store, ok := s.(storetypes.ObjKVStore)
+ if !ok {
+ panic(fmt.Sprintf("store with key %v is not ObjKVStore", key))
+ }
+
+ return store
+}
+
+func (db *CommitMultiTree) LastCommitID() storetypes.CommitID {
+ return db.lastCommitId
+}
+
+func (db *CommitMultiTree) WorkingHash() []byte {
+ // NOTE: this may invoke some hash recomputation each time even if there is no change
+ stagedVersion := db.version + 1
+ hashes := make([][]byte, len(db.trees))
+ var wg sync.WaitGroup
+ for i, tree := range db.trees {
+ wg.Add(1)
+ go func(i int, t storetypes.CommitStore) {
+ defer wg.Done()
+ hashes[i] = t.WorkingHash()
+ }(i, tree)
+ }
+ wg.Wait()
+
+ commitInfo := &storetypes.CommitInfo{}
+ commitInfo.StoreInfos = make([]storetypes.StoreInfo, len(db.treeKeys))
+ for i, treeKey := range db.treeKeys {
+ commitInfo.StoreInfos[i] = storetypes.StoreInfo{
+ Name: treeKey.Name(),
+ CommitId: storetypes.CommitID{
+ Version: int64(stagedVersion),
+ Hash: hashes[i],
+ },
+ }
+ }
+ db.workingCommitInfo = commitInfo
+ hash := commitInfo.Hash()
+ db.workingHash = hash
+ return hash
+}
+
+func (db *CommitMultiTree) Commit() storetypes.CommitID {
+ // NOTE: this function is maybe unnecessarily complex because the SDK has both WorkingHash and Commit methods
+ // and we're trying to avoid recomputing the hash
+ // so we check if we already have a hash that was computed in WorkingHash that hasn't changed to avoid recomputation
+ // in the future we should evaluate if there is any need to retain both WorkingHash and Commit methods separately
+ hashes := make([][]byte, len(db.trees))
+ var wg sync.WaitGroup
+ for i, tree := range db.trees {
+ wg.Add(1)
+ go func(i int, t storetypes.CommitStore) {
+ defer wg.Done()
+ hashes[i] = t.Commit().Hash
+ }(i, tree)
+ }
+ wg.Wait()
+
+ stagedVersion := db.version + 1
+ commitInfo := db.workingCommitInfo
+ var hash []byte
+ if commitInfo == nil {
+ commitInfo = &storetypes.CommitInfo{}
+ commitInfo.StoreInfos = make([]storetypes.StoreInfo, len(db.treeKeys))
+ for i, treeKey := range db.treeKeys {
+ commitInfo.StoreInfos[i] = storetypes.StoreInfo{
+ Name: treeKey.Name(),
+ CommitId: storetypes.CommitID{
+ Version: int64(stagedVersion),
+ Hash: hashes[i],
+ },
+ }
+ }
+ hash = commitInfo.Hash()
+ } else {
+ hashChanged := false
+ for i, storeInfo := range commitInfo.StoreInfos {
+ if !bytes.Equal(storeInfo.CommitId.Hash, hashes[i]) {
+ hashChanged = true
+ commitInfo.StoreInfos[i].CommitId.Hash = hashes[i]
+ }
+ }
+ if !hashChanged {
+ hash = db.workingHash
+ } else {
+ hash = commitInfo.Hash()
+ }
+ db.workingCommitInfo = nil
+ db.workingHash = nil
+ }
+
+ db.version++
+ commitId := storetypes.CommitID{
+ Version: int64(db.version),
+ Hash: hash,
+ }
+ db.lastCommitId = commitId
+ return commitId
+}
+
+func (db *CommitMultiTree) SetPruning(options pruningtypes.PruningOptions) {
+ db.logger.Warn("SetPruning is not implemented for CommitMultiTree")
+}
+
+func (db *CommitMultiTree) GetPruning() pruningtypes.PruningOptions {
+ return pruningtypes.NewPruningOptions(pruningtypes.PruningDefault)
+}
+
+func (db *CommitMultiTree) GetStoreType() storetypes.StoreType {
+ return storetypes.StoreTypeMulti
+}
+
+func (db *CommitMultiTree) CacheWrap() storetypes.CacheWrap {
+ return db.CacheMultiStore()
+}
+
+func (db *CommitMultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap {
+ // TODO implement tracking
+ return db.CacheMultiStore()
+}
+
+func (db *CommitMultiTree) CacheMultiStore() storetypes.CacheMultiStore {
+ mt := &MultiTree{
+ trees: make([]storetypes.CacheWrap, len(db.trees)),
+ treesByKey: db.treesByKey, // share the map
+ }
+ for i, tree := range db.trees {
+ mt.trees[i] = tree.CacheWrap()
+ }
+ return mt
+}
+
+func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) {
+ if version == 0 {
+ version = int64(db.version)
+ }
+
+ mt := &MultiTree{
+ latestVersion: version,
+ treesByKey: db.treesByKey, // share the map
+ trees: make([]storetypes.CacheWrap, len(db.trees)),
+ }
+
+ for i, tree := range db.trees {
+ typ := db.storeTypes[i]
+ switch typ {
+ case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB:
+ t, err := tree.(*CommitTree).GetImmutable(version)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create cache multi store for tree %s at version %d: %w", db.treeKeys[i].Name(), version, err)
+ }
+ mt.trees[i] = t.CacheWrap()
+ default:
+ mt.trees[i] = tree.CacheWrap()
+ }
+ }
+
+ return mt, nil
+}
+
+func (db *CommitMultiTree) GetStore(key storetypes.StoreKey) storetypes.Store {
+ return db.trees[db.treesByKey[key]]
+}
+
+func (db *CommitMultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore {
+ index, ok := db.treesByKey[key]
+ if !ok {
+ panic(fmt.Sprintf("store not found for key: %s (key type: %T)", key.Name(), key))
+ }
+ if index >= len(db.trees) {
+ panic(fmt.Sprintf("store index %d out of bounds for key %s (trees length: %d)", index, key.Name(), len(db.trees)))
+ }
+ s := db.trees[index]
+ store, ok := s.(storetypes.KVStore)
+ if !ok {
+ panic(fmt.Sprintf("store with key %v is not KVStore", key))
+ }
+ return store
+}
+
+func (db *CommitMultiTree) TracingEnabled() bool {
+ return false
+}
+
+func (db *CommitMultiTree) SetTracer(w io.Writer) storetypes.MultiStore {
+ db.logger.Warn("SetTracer is not implemented for CommitMultiTree")
+ return db
+}
+
+func (db *CommitMultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore {
+ db.logger.Warn("SetTracingContext is not implemented for CommitMultiTree")
+ return db
+}
+
+func (db *CommitMultiTree) Snapshot(height uint64, protoWriter protoio.Writer) error {
+ return fmt.Errorf("snapshotting has not been implemented yet")
+}
+
+func (db *CommitMultiTree) PruneSnapshotHeight(height int64) {
+ db.logger.Warn("PruneSnapshotHeight is not implemented for CommitMultiTree")
+}
+
+func (db *CommitMultiTree) SetSnapshotInterval(snapshotInterval uint64) {
+ db.logger.Warn("SetSnapshotInterval is not implemented for CommitMultiTree")
+}
+
+func (db *CommitMultiTree) Restore(height uint64, format uint32, protoReader protoio.Reader) (snapshottypes.SnapshotItem, error) {
+ return snapshottypes.SnapshotItem{}, fmt.Errorf("restoring from snapshot has not been implemented yet")
+}
+
+func (db *CommitMultiTree) MountStoreWithDB(key storetypes.StoreKey, typ storetypes.StoreType, _ dbm.DB) {
+ if _, exists := db.treesByKey[key]; exists {
+ panic(fmt.Sprintf("store with key %s already mounted", key.Name()))
+ }
+ index := len(db.treeKeys)
+ db.treesByKey[key] = index
+ db.treeKeys = append(db.treeKeys, key)
+ db.storeTypes = append(db.storeTypes, typ)
+}
+
+func (db *CommitMultiTree) GetCommitStore(key storetypes.StoreKey) storetypes.CommitStore {
+ return db.trees[db.treesByKey[key]]
+}
+
+func (db *CommitMultiTree) GetCommitKVStore(key storetypes.StoreKey) storetypes.CommitKVStore {
+ s := db.trees[db.treesByKey[key]]
+ store, ok := s.(storetypes.CommitKVStore)
+ if !ok {
+ panic(fmt.Sprintf("store with key %s is not CommitKVStore", key.Name()))
+ }
+ return store
+}
+
+func (db *CommitMultiTree) LoadLatestVersion() error {
+ for i, key := range db.treeKeys {
+ storeType := db.storeTypes[i]
+ tree, err := db.loadStore(key, storeType)
+ if err != nil {
+ return fmt.Errorf("failed to load store %s: %w", key.Name(), err)
+ }
+ db.trees = append(db.trees, tree)
+ }
+ return nil
+}
+
+func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.StoreType) (storetypes.CommitStore, error) {
+ switch typ {
+ case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB:
+ dir := filepath.Join(db.dir, key.Name())
+ if _, err := os.Stat(dir); !os.IsNotExist(err) {
+ return nil, fmt.Errorf("store directory %s already exists, reloading isn't supported yet", dir)
+ }
+ err := os.MkdirAll(dir, 0o755)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create store dir %s: %w", dir, err)
+ }
+ return NewCommitTree(dir, db.opts, db.logger.With("store", key.Name()))
+ case storetypes.StoreTypeTransient:
+ _, ok := key.(*storetypes.TransientStoreKey)
+ if !ok {
+ return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String())
+ }
+
+ return transient.NewStore(), nil
+ case storetypes.StoreTypeMemory:
+ if _, ok := key.(*storetypes.MemoryStoreKey); !ok {
+ return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String())
+ }
+
+ return mem.NewStore(), nil
+ case storetypes.StoreTypeObject:
+ if _, ok := key.(*storetypes.ObjectStoreKey); !ok {
+ return nil, fmt.Errorf("unexpected key type for a ObjectStoreKey: %s", key.String())
+ }
+ return transient.NewObjStore(), nil
+ default:
+ return nil, fmt.Errorf("unsupported store type: %s", typ.String())
+ }
+}
+
+func (db *CommitMultiTree) LoadLatestVersionAndUpgrade(upgrades *storetypes.StoreUpgrades) error {
+ return fmt.Errorf("LoadLatestVersionAndUpgrade has not been implemented yet")
+}
+
+func (db *CommitMultiTree) LoadVersionAndUpgrade(ver int64, upgrades *storetypes.StoreUpgrades) error {
+ return fmt.Errorf("LoadVersionAndUpgrade has not been implemented yet")
+}
+
+func (db *CommitMultiTree) LoadVersion(ver int64) error {
+ return fmt.Errorf("LoadVersion has not been implemented yet")
+}
+
+func (db *CommitMultiTree) SetInterBlockCache(cache storetypes.MultiStorePersistentCache) {
+ db.logger.Warn("SetInterBlockCache is not implemented for CommitMultiTree")
+}
+
+func (db *CommitMultiTree) SetInitialVersion(version int64) error {
+ return fmt.Errorf("SetInitialVersion has not been implemented yet")
+}
+
+func (db *CommitMultiTree) SetIAVLCacheSize(size int) {
+}
+
+func (db *CommitMultiTree) SetIAVLDisableFastNode(disable bool) {
+}
+
+func (db *CommitMultiTree) SetIAVLSyncPruning(sync bool) {
+}
+
+func (db *CommitMultiTree) RollbackToVersion(version int64) error {
+ return fmt.Errorf("RollbackToVersion has not been implemented yet")
+}
+
+func (db *CommitMultiTree) ListeningEnabled(key storetypes.StoreKey) bool {
+ db.logger.Warn("ListeningEnabled is not implemented for CommitMultiTree")
+ return false
+}
+
+func (db *CommitMultiTree) AddListeners(keys []storetypes.StoreKey) {
+ db.logger.Warn("AddListeners is not implemented for CommitMultiTree")
+}
+
+func (db *CommitMultiTree) PopStateCache() []*storetypes.StoreKVPair {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (db *CommitMultiTree) SetMetrics(metrics metrics.StoreMetrics) {
+ db.logger.Warn("SetMetrics is not implemented for CommitMultiTree")
+}
+
+func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, error) {
+ // n := len(treeNames)
+ //trees := make([]*CommitTree, n)
+ //treesByName := make(map[string]int, n)
+ //for i, name := range treeNames {
+ // if _, exists := treesByName[name]; exists {
+ // return nil, fmt.Errorf("duplicate tree name: %s", name)
+ // }
+ // treesByName[name] = i
+ // dir := filepath.Join(path, name)
+ // err := os.MkdirAll(dir, 0o755)
+ // if err != nil {
+ // return nil, fmt.Errorf("failed to create tree dir %s: %w", dir, err)
+ // }
+ // // Create a logger with tree name context
+ // treeLogger := logger.With("tree", name)
+ // trees[i], err = NewCommitTree(dir, *opts, treeLogger)
+ // if err != nil {
+ // return nil, fmt.Errorf("failed to load tree %s: %w", name, err)
+ // }
+ //}
+ //
+ db := &CommitMultiTree{
+ dir: path,
+ opts: *opts,
+ logger: logger,
+ treesByKey: make(map[storetypes.StoreKey]int),
+ }
+ return db, nil
+}
+
+func (db *CommitMultiTree) stagedVersion() uint64 {
+ return db.version + 1
+}
+
+func (db *CommitMultiTree) LatestVersion() int64 {
+ return int64(db.version)
+}
+
+func (db *CommitMultiTree) Close() error {
+ for _, tree := range db.trees {
+ if closer, ok := tree.(io.Closer); ok {
+ err := closer.Close()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ return nil
+}
+
+var _ storetypes.CommitMultiStore = &CommitMultiTree{}
diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go
new file mode 100644
index 000000000000..9c20c5c88b00
--- /dev/null
+++ b/iavlx/commit_tree.go
@@ -0,0 +1,451 @@
+package iavlx
+
+import (
+ "fmt"
+ "io"
+ "sync"
+ "sync/atomic"
+
+ "cosmossdk.io/log"
+
+ pruningtypes "cosmossdk.io/store/pruning/types"
+ storetypes "cosmossdk.io/store/types"
+)
+
+type CommitTree struct {
+ latest atomic.Pointer[NodePointer]
+ root *NodePointer
+ writeMutex sync.Mutex
+ store *TreeStore
+ zeroCopy bool
+
+ evictionDepth uint8
+ evictorRunning atomic.Bool
+ lastEvictVersion uint32
+
+ writeWal bool
+ walQueue *NonBlockingQueue[[]KVUpdate]
+ walDone <-chan error
+
+ pendingOrphans [][]NodeID
+
+ logger log.Logger
+
+ lastCommitId storetypes.CommitID
+ commitCtx *commitContext
+}
+
+func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, error) {
+ ts, err := NewTreeStore(dir, opts, logger)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create tree store: %w", err)
+ }
+
+ var root *NodePointer
+ var lastCommitId storetypes.CommitID
+ savedVersion := ts.SavedVersion()
+ if savedVersion > 0 {
+ root, err = ts.ResolveRoot(savedVersion)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve root for saved version %d: %w", savedVersion, err)
+ }
+ if root != nil {
+ rootNode, err := root.Resolve()
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve root node for saved version %d: %w", savedVersion, err)
+ }
+ hash := rootNode.Hash()
+ lastCommitId = storetypes.CommitID{
+ Version: int64(savedVersion),
+ Hash: hash,
+ }
+ }
+ }
+
+ tree := &CommitTree{
+ store: ts,
+ root: root,
+ lastCommitId: lastCommitId,
+ zeroCopy: opts.ZeroCopy,
+ logger: logger,
+ evictionDepth: opts.EvictDepth,
+ writeWal: opts.WriteWAL,
+ }
+ tree.latest.Store(root)
+ tree.reinitWalProc()
+
+ return tree, nil
+}
+
+func (c *CommitTree) WorkingHash() []byte {
+ c.writeMutex.Lock()
+ defer c.writeMutex.Unlock()
+
+ return c.workingHash()
+}
+
+func (c *CommitTree) workingHash() []byte {
+ // IMPORTANT: this function assumes the write lock is held
+
+ // if we have no root, return empty hash
+ if c.root == nil {
+ c.commitCtx = nil
+ return emptyHash
+ }
+
+ root := c.root.mem.Load()
+ if root != nil && root.hash != nil {
+ // already computed working hash
+ return root.hash
+ }
+
+ savedVersion := c.store.SavedVersion()
+ stagedVersion := c.store.stagedVersion
+ c.commitCtx = &commitContext{
+ version: stagedVersion,
+ savedVersion: savedVersion,
+ }
+
+ // compute hash and assign node IDs
+ hash, err := commitTraverse(c.commitCtx, c.root, 0)
+ if err != nil {
+ panic(fmt.Sprintf("failed to compute working hash: %v", err))
+ }
+ return hash
+}
+
+func (c *CommitTree) Commit() storetypes.CommitID {
+ commitId, err := c.commit()
+ if err != nil {
+ panic(fmt.Sprintf("failed to commit: %v", err))
+ }
+ return commitId
+}
+
+func (c *CommitTree) commit() (storetypes.CommitID, error) {
+ c.writeMutex.Lock()
+ defer c.writeMutex.Unlock()
+
+ if c.writeWal {
+ c.walQueue.Close()
+ }
+
+ // compute hash and assign node IDs
+ hash := c.workingHash()
+
+ stagedVersion := c.store.stagedVersion
+ if c.writeWal {
+ // wait for WAL write to complete
+ err := <-c.walDone
+ if err != nil {
+ return storetypes.CommitID{}, err
+ }
+
+ err = c.store.WriteWALCommit(stagedVersion)
+ if err != nil {
+ return storetypes.CommitID{}, err
+ }
+
+ c.reinitWalProc()
+ }
+
+ commitCtx := c.commitCtx
+ if commitCtx == nil {
+ // make sure we have a non-nil commit context
+ commitCtx = &commitContext{}
+ }
+ err := c.store.SaveRoot(c.root, commitCtx.leafNodeIdx, commitCtx.branchNodeIdx)
+ if err != nil {
+ return storetypes.CommitID{}, err
+ }
+
+ c.store.MarkOrphans(stagedVersion, c.pendingOrphans)
+ c.pendingOrphans = nil
+
+ // start eviction if needed
+ c.startEvict(c.store.SavedVersion())
+
+ // cache the committed tree as the latest version
+ c.latest.Store(c.root)
+ commitId := storetypes.CommitID{
+ Version: int64(stagedVersion),
+ Hash: hash,
+ }
+ c.lastCommitId = commitId
+ c.commitCtx = nil
+
+ return commitId, nil
+}
+
+func (c *CommitTree) LastCommitID() storetypes.CommitID {
+ return c.lastCommitId
+}
+
+func (c *CommitTree) SetPruning(pruningtypes.PruningOptions) {}
+
+func (c *CommitTree) GetPruning() pruningtypes.PruningOptions {
+ return pruningtypes.NewPruningOptions(pruningtypes.PruningDefault)
+}
+
+func (c *CommitTree) GetStoreType() storetypes.StoreType {
+ return storetypes.StoreTypeIAVL
+}
+
+func (c *CommitTree) CacheWrap() storetypes.CacheWrap {
+ return NewCacheTree(c)
+}
+
+func (c *CommitTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap {
+ // TODO support tracing
+ return c.CacheWrap()
+}
+
+func (c *CommitTree) Get(key []byte) []byte {
+ if c.root == nil {
+ return nil
+ }
+
+ root, err := c.root.Resolve()
+ if err != nil {
+ panic(err)
+ }
+
+ value, _, err := root.Get(key)
+ if err != nil {
+ panic(err)
+ }
+
+ return value
+}
+
+func (c *CommitTree) Has(key []byte) bool {
+ return c.Get(key) != nil
+}
+
+func (c *CommitTree) Set(key, value []byte) {
+ storetypes.AssertValidKey(key)
+ storetypes.AssertValidValue(value)
+
+ c.writeMutex.Lock()
+ defer c.writeMutex.Unlock()
+
+ stagedVersion := c.store.stagedVersion
+ leafNode := &MemNode{
+ height: 0,
+ size: 1,
+ version: stagedVersion,
+ key: key,
+ value: value,
+ }
+
+ if c.writeWal {
+ // start writing this to the WAL asynchronously before we even mutate the tree
+ c.walQueue.Send([]KVUpdate{{SetNode: leafNode}})
+ }
+
+ ctx := &MutationContext{Version: stagedVersion}
+ newRoot, _, err := setRecursive(c.root, leafNode, ctx)
+ if err != nil {
+ panic(err)
+ }
+
+ c.root = newRoot
+ c.pendingOrphans = append(c.pendingOrphans, ctx.Orphans)
+}
+
+func (c *CommitTree) Delete(key []byte) {
+ storetypes.AssertValidKey(key)
+
+ c.writeMutex.Lock()
+ defer c.writeMutex.Unlock()
+
+ if c.writeWal {
+ // start writing this to the WAL asynchronously before we even mutate the tree
+ c.walQueue.Send([]KVUpdate{{DeleteKey: key}})
+ }
+
+ ctx := &MutationContext{Version: c.store.stagedVersion}
+ _, newRoot, _, err := removeRecursive(c.root, key, ctx)
+ if err != nil {
+ panic(err)
+ }
+ c.root = newRoot
+ c.pendingOrphans = append(c.pendingOrphans, ctx.Orphans)
+}
+
+func (c *CommitTree) Iterator(start, end []byte) storetypes.Iterator {
+ return NewIterator(start, end, true, c.root, c.zeroCopy)
+}
+
+func (c *CommitTree) ReverseIterator(start, end []byte) storetypes.Iterator {
+ return NewIterator(start, end, false, c.root, c.zeroCopy)
+}
+
+func (c *CommitTree) reinitWalProc() {
+ if !c.writeWal {
+ return
+ }
+
+ walQueue := NewNonBlockingQueue[[]KVUpdate]()
+ walDone := make(chan error, 1)
+ c.walQueue = walQueue
+ c.walDone = walDone
+
+ go func() {
+ for {
+ batch := walQueue.Receive()
+ if batch == nil {
+ close(walDone)
+ return
+ }
+ for _, updates := range batch {
+ err := c.store.WriteWALUpdates(updates)
+ if err != nil {
+ walDone <- err
+ return
+ }
+ }
+ }
+ }()
+}
+
+func (c *CommitTree) startEvict(evictVersion uint32) {
+ if c.evictorRunning.Load() {
+ // eviction in progress
+ return
+ }
+
+ if evictVersion <= c.lastEvictVersion {
+ // no new version to evict
+ return
+ }
+
+ latest := c.latest.Load()
+ if latest == nil {
+ // nothing to evict
+ return
+ }
+
+ c.logger.Debug("start eviction", "version", evictVersion, "depth", c.evictionDepth)
+ c.evictorRunning.Store(true)
+ go func() {
+ evictedCount := evictTraverse(latest, 0, c.evictionDepth, evictVersion)
+ c.logger.Debug("eviction completed", "version", evictVersion, "lastEvict", c.lastEvictVersion, "evictedNodes", evictedCount)
+ c.lastEvictVersion = evictVersion
+ c.evictorRunning.Store(false)
+ }()
+}
+
+func (c *CommitTree) GetImmutable(version int64) (storetypes.KVStore, error) {
+ var rootPtr *NodePointer
+ if version == c.lastCommitId.Version {
+ rootPtr = c.root
+ } else {
+ var err error
+ rootPtr, err = c.store.ResolveRoot(uint32(version))
+ if err != nil {
+ return nil, err
+ }
+ }
+ return NewImmutableTree(rootPtr), nil
+}
+
+func (c *CommitTree) ResolveRoot(version uint32) (*NodePointer, error) {
+ if version == 0 {
+ version = c.store.stagedVersion - 1
+ }
+ return c.store.ResolveRoot(version)
+}
+
+func (c *CommitTree) Version() uint32 {
+ return c.store.stagedVersion - 1
+}
+
+func (c *CommitTree) Close() error {
+ if c.walQueue != nil {
+ c.walQueue.Close()
+ // TODO do we need to wait for WAL done??
+ }
+ return c.store.Close()
+}
+
+type commitContext struct {
+ version uint32
+ savedVersion uint32
+ branchNodeIdx uint32
+ leafNodeIdx uint32
+}
+
+// commitTraverse performs a post-order traversal of the tree to compute hashes and assign node IDs.
+// if it is run multiple times and the tree has been mutated before being committed, node IDs will be reassigned.
+func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []byte, err error) {
+ memNode := np.mem.Load()
+ if memNode == nil {
+ node, err := np.Resolve()
+ if err != nil {
+ return nil, err
+ }
+ return node.Hash(), nil
+ }
+
+ if memNode.version != ctx.version {
+ return memNode.hash, nil
+ }
+
+ var leftHash, rightHash []byte
+ var id NodeID
+ if memNode.IsLeaf() {
+ ctx.leafNodeIdx++
+ id = NewNodeID(true, uint64(ctx.version), ctx.leafNodeIdx)
+ } else {
+ // post-order traversal
+ leftHash, err = commitTraverse(ctx, memNode.left, depth+1)
+ if err != nil {
+ return nil, err
+ }
+ rightHash, err = commitTraverse(ctx, memNode.right, depth+1)
+ if err != nil {
+ return nil, err
+ }
+
+ ctx.branchNodeIdx++
+ id = NewNodeID(false, uint64(ctx.version), ctx.branchNodeIdx)
+ }
+ np.id = id
+ memNode.nodeId = id
+
+ if memNode.hash != nil {
+ // hash previously computed node
+ return memNode.hash, nil
+ }
+
+ return computeAndSetHash(memNode, leftHash, rightHash)
+}
+
+func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uint32) (count int) {
+ // TODO check height, and don't traverse if tree is too short
+
+ memNode := np.mem.Load()
+ if memNode == nil {
+ return 0
+ }
+
+ // Evict nodes at or below the eviction depth
+ if memNode.version <= evictVersion && depth >= evictionDepth {
+ np.mem.Store(nil)
+ count = 1
+ }
+
+ if memNode.IsLeaf() {
+ return count
+ }
+
+ // Continue traversing to find nodes to evict
+ count += evictTraverse(memNode.left, depth+1, evictionDepth, evictVersion)
+ count += evictTraverse(memNode.right, depth+1, evictionDepth, evictVersion)
+ return count
+}
+
+var (
+ _ storetypes.CommitStore = &CommitTree{}
+)
diff --git a/iavlx/compactor.go b/iavlx/compactor.go
new file mode 100644
index 000000000000..da90a3e09e34
--- /dev/null
+++ b/iavlx/compactor.go
@@ -0,0 +1,347 @@
+package iavlx
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "log/slog"
+)
+
+type CompactOptions struct {
+ RetainCriteria RetainCriteria
+ CompactWAL bool
+ CompactedAt uint32 // version at which compaction is done
+}
+
+type RetainCriteria func(createVersion, orphanVersion uint32) bool
+
+type Compactor struct {
+ criteria RetainCriteria
+ compactWAL bool
+
+ processedChangesets []*Changeset
+ treeStore *TreeStore
+
+ originalKvLogPath string
+ files *ChangesetFiles
+ leavesWriter *StructWriter[LeafLayout]
+ branchesWriter *StructWriter[BranchLayout]
+ versionsWriter *StructWriter[VersionInfo]
+ kvlogWriter *KVLogWriter
+ orphanWriter *OrphanWriter
+
+ keyCache map[string]uint32
+ // offsetCache holds the updated 1-based offsets of nodes affected by compacting.
+ // these are then used to update BranchLayout's left and right offsets.
+ offsetCache map[NodeID]uint32
+
+ // Running totals across all processed changesets
+ leafOrphanCount uint32
+ branchOrphanCount uint32
+ leafOrphanVersionTotal uint64
+ branchOrphanVersionTotal uint64
+ ctx context.Context
+}
+
+func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) {
+ if reader.files == nil {
+ return nil, fmt.Errorf("changeset has no associated files, cannot compact a shared changeset reader which files set to nil")
+ }
+ files := reader.files
+ startingVersion := files.StartVersion()
+ lastCompactedAt := files.CompactedAtVersion()
+ if lastCompactedAt >= opts.CompactedAt {
+ return nil, fmt.Errorf("cannot compact changeset starting at version %d which was last compacted at %d to an earlier or same version %d",
+ startingVersion, lastCompactedAt, opts.CompactedAt)
+ }
+
+ // if we're not compacting the WAL, we can reuse the existing KV log path
+ kvlogPath := reader.files.KVLogPath()
+ // if we're compacting the WAL, create a new KV log path
+ if opts.CompactWAL {
+ kvlogPath = ""
+ }
+
+ newFiles, err := CreateChangesetFiles(files.TreeDir(), files.StartVersion(), opts.CompactedAt, kvlogPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open new changeset files: %w", err)
+ }
+
+ var kvlogWriter *KVLogWriter
+ // we only need a new KV log writer if we're compacting the WAL, otherwise it should be nil
+ if opts.CompactWAL {
+ kvlogWriter = NewKVDataWriter(newFiles.kvlogFile)
+ }
+
+ c := &Compactor{
+ ctx: ctx,
+ criteria: opts.RetainCriteria,
+ compactWAL: opts.CompactWAL,
+ treeStore: store,
+ files: newFiles,
+ originalKvLogPath: reader.files.KVLogPath(),
+ kvlogWriter: kvlogWriter,
+ leavesWriter: NewStructWriter[LeafLayout](newFiles.leavesFile),
+ branchesWriter: NewStructWriter[BranchLayout](newFiles.branchesFile),
+ versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile),
+ orphanWriter: NewOrphanWriter(newFiles.orphansFile),
+ keyCache: make(map[string]uint32),
+ offsetCache: make(map[NodeID]uint32),
+ }
+
+ // Process first changeset immediately
+ err = c.processChangeset(reader)
+ if err != nil {
+ return nil, fmt.Errorf("failed to process initial changeset: %w", err)
+ }
+
+ return c, nil
+}
+
+func (c *Compactor) processChangeset(reader *Changeset) error {
+ // Compute KV offset delta for non-CompactWAL mode
+ kvOffsetDelta := uint32(0)
+ if c.kvlogWriter != nil && !c.compactWAL {
+ kvOffsetDelta = uint32(c.kvlogWriter.Size())
+ }
+
+ versionsData := reader.versionsData
+ numVersions := versionsData.Count()
+ leavesData := reader.leavesData
+ branchesData := reader.branchesData
+
+ // flush orphan writer to ensure all orphans are written before reading
+ err := reader.orphanWriter.Flush()
+ if err != nil {
+ return fmt.Errorf("failed to flush orphan writer before reading orphan map: %w", err)
+ }
+ orphanMap, err := ReadOrphanMap(reader.files.orphansFile)
+ if err != nil {
+ return fmt.Errorf("failed to read orphan map: %w", err)
+ }
+
+ slog.DebugContext(c.ctx, "processing changeset for compaction", "versions", numVersions)
+ for i := 0; i < numVersions; i++ {
+ verInfo := *versionsData.UnsafeItem(uint32(i)) // copy
+ newLeafStartIdx := uint32(0)
+ newLeafEndIdx := uint32(0)
+ leafStartOffset := verInfo.Leaves.StartOffset
+ leafCount := verInfo.Leaves.Count
+ newLeafStartOffset := uint32(c.leavesWriter.Count())
+ newLeafCount := uint32(0)
+ // Iterate leaves
+ // For each leaf, check if it should be retained
+ for j := uint32(0); j < leafCount; j++ {
+ leaf := *leavesData.UnsafeItem(leafStartOffset + j) // copy
+ id := leaf.Id
+ orphanVersion := orphanMap[id]
+ retain := orphanVersion == 0 || c.criteria(uint32(id.Version()), orphanVersion)
+ if !retain {
+ continue
+ }
+
+ if orphanVersion != 0 {
+ c.leafOrphanCount++
+ c.leafOrphanVersionTotal += uint64(orphanVersion)
+ }
+
+ if newLeafStartIdx == 0 {
+ newLeafStartIdx = id.Index()
+ }
+ newLeafEndIdx = id.Index()
+ newLeafCount++
+
+ if c.compactWAL {
+ k, v, err := reader.ReadKV(id, leaf.KeyOffset)
+ if err != nil {
+ return fmt.Errorf("failed to read KV for leaf %s: %w", id, err)
+ }
+
+ offset, err := c.kvlogWriter.WriteKV(k, v)
+ if err != nil {
+ return fmt.Errorf("failed to write KV for leaf %s: %w", id, err)
+ }
+
+ leaf.KeyOffset = offset
+ c.keyCache[unsafeBytesToString(k)] = offset
+ } else {
+ // When not compacting WAL, add offset delta
+ leaf.KeyOffset += kvOffsetDelta
+ }
+
+ err := c.leavesWriter.Append(&leaf)
+ if err != nil {
+ return fmt.Errorf("failed to append leaf %s: %w", id, err)
+ }
+
+ c.offsetCache[id] = uint32(c.leavesWriter.Count())
+
+ if orphanVersion != 0 {
+ if err := c.orphanWriter.WriteOrphan(orphanVersion, id); err != nil {
+ return fmt.Errorf("failed to write retained orphan leaf %s: %w", id, err)
+ }
+ }
+ }
+
+ newBranchStartIdx := uint32(0)
+ newBranchEndIdx := uint32(0)
+ branchStartOffset := verInfo.Branches.StartOffset
+ branchCount := verInfo.Branches.Count
+ newBranchStartOffset := uint32(c.branchesWriter.Count())
+ newBranchCount := uint32(0)
+ for j := uint32(0); j < branchCount; j++ {
+ branch := *branchesData.UnsafeItem(branchStartOffset + j) // copy
+ id := branch.Id
+ orphanVersion := orphanMap[id]
+ retain := orphanVersion == 0 || c.criteria(uint32(id.Version()), orphanVersion)
+ if !retain {
+ continue
+ }
+
+ if orphanVersion != 0 {
+ c.branchOrphanCount++
+ c.branchOrphanVersionTotal += uint64(orphanVersion)
+ }
+
+ if newBranchStartIdx == 0 {
+ newBranchStartIdx = id.Index()
+ }
+ newBranchEndIdx = id.Index()
+ newBranchCount++
+
+ if newLeftOffset, ok := c.offsetCache[branch.Left]; ok {
+ branch.LeftOffset = newLeftOffset
+ }
+ if newRightOffset, ok := c.offsetCache[branch.Right]; ok {
+ branch.RightOffset = newRightOffset
+ }
+
+ if c.compactWAL {
+ k, err := reader.ReadK(id, branch.KeyOffset)
+ if err != nil {
+ return fmt.Errorf("failed to read key for branch %s: %w", id, err)
+ }
+ offset, ok := c.keyCache[unsafeBytesToString(k)]
+ if !ok {
+ offset, err = c.kvlogWriter.WriteK(k)
+ }
+ if err != nil {
+ return fmt.Errorf("failed to write key for branch %s: %w", id, err)
+ }
+ branch.KeyOffset = offset
+ } else {
+ // When not compacting WAL, add offset delta
+ branch.KeyOffset += kvOffsetDelta
+ }
+
+ err := c.branchesWriter.Append(&branch)
+ if err != nil {
+ return fmt.Errorf("failed to append branch %s: %w", id, err)
+ }
+ c.offsetCache[id] = uint32(c.branchesWriter.Count())
+
+ if orphanVersion != 0 {
+ if err := c.orphanWriter.WriteOrphan(orphanVersion, id); err != nil {
+ return fmt.Errorf("failed to write retained orphan leaf %s: %w", id, err)
+ }
+ }
+ }
+
+ verInfo = VersionInfo{
+ Leaves: NodeSetInfo{
+ StartIndex: newLeafStartIdx,
+ EndIndex: newLeafEndIdx,
+ StartOffset: newLeafStartOffset,
+ Count: newLeafCount,
+ },
+ Branches: NodeSetInfo{
+ StartIndex: newBranchStartIdx,
+ EndIndex: newBranchEndIdx,
+ StartOffset: newBranchStartOffset,
+ Count: newBranchCount,
+ },
+ RootID: verInfo.RootID,
+ }
+
+ err := c.versionsWriter.Append(&verInfo)
+ if err != nil {
+ return fmt.Errorf("failed to append version info for version %d: %w", reader.files.info.StartVersion+uint32(i), err)
+ }
+ }
+
+ // Track this changeset as processed
+ c.processedChangesets = append(c.processedChangesets, reader)
+
+ return nil
+}
+
+func (c *Compactor) AddChangeset(cs *Changeset) error {
+ // TODO: Support joining changesets when CompactWAL=false
+ // This requires copying the entire KV log and tracking cumulative offsets
+ if !c.compactWAL {
+ return fmt.Errorf("joining changesets is only supported when CompactWAL=true")
+ }
+ return c.processChangeset(cs)
+}
+
+func (c *Compactor) Seal() (*Changeset, error) {
+ if len(c.processedChangesets) == 0 {
+ return nil, fmt.Errorf("no changesets processed")
+ }
+
+ info := c.files.info
+ info.StartVersion = c.processedChangesets[0].files.info.StartVersion
+ info.EndVersion = c.processedChangesets[len(c.processedChangesets)-1].files.info.EndVersion
+ info.LeafOrphans = c.leafOrphanCount
+ info.BranchOrphans = c.branchOrphanCount
+ info.LeafOrphanVersionTotal = c.leafOrphanVersionTotal
+ info.BranchOrphanVersionTotal = c.branchOrphanVersionTotal
+
+ errs := []error{
+ c.leavesWriter.Flush(),
+ c.branchesWriter.Flush(),
+ c.versionsWriter.Flush(),
+ c.orphanWriter.Flush(),
+ c.files.RewriteInfo(),
+ }
+ if c.kvlogWriter != nil {
+ errs = append(errs, c.kvlogWriter.Flush())
+ }
+ if err := errors.Join(errs...); err != nil {
+ return nil, fmt.Errorf("failed to flush data during compaction seal: %w", err)
+ }
+ if err := c.files.MarkReady(); err != nil {
+ return nil, fmt.Errorf("failed to mark changeset as ready during compaction seal: %w", err)
+ }
+
+ cs := NewChangeset(c.treeStore)
+ err := cs.InitOwned(c.files)
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize sealed changeset: %w", err)
+ }
+
+ // write orphan map
+ if err != nil {
+ return nil, fmt.Errorf("failed to write orphan map during compaction seal: %w", err)
+ }
+
+ return cs, nil
+}
+
+func (c *Compactor) Abort() error {
+ err := c.files.Close()
+ if err != nil {
+ return fmt.Errorf("failed to close compactor files during cleanup: %w", err)
+ }
+ return c.files.DeleteFiles(ChangesetDeleteArgs{
+ SaveKVLogPath: c.originalKvLogPath,
+ })
+}
+
+func (c *Compactor) TotalBytes() int {
+ total := c.leavesWriter.Size() + c.branchesWriter.Size() + c.versionsWriter.Size()
+ if c.kvlogWriter != nil {
+ total += c.kvlogWriter.Size()
+ }
+ return total
+}
diff --git a/iavlx/dot_graph.go b/iavlx/dot_graph.go
new file mode 100644
index 000000000000..3b0086e278be
--- /dev/null
+++ b/iavlx/dot_graph.go
@@ -0,0 +1,239 @@
+package iavlx
+
+import (
+ "fmt"
+ "io"
+)
+
+func DebugTraverseNode(nodePtr *NodePointer, onNode func(node, parent Node, direction string) error) error {
+ if nodePtr == nil {
+ return nil
+ }
+
+ var traverse func(np *NodePointer, parent Node, direction string) error
+ traverse = func(np *NodePointer, parent Node, direction string) error {
+ node, err := np.Resolve()
+ if err != nil {
+ return err
+ }
+
+ if err := onNode(node, parent, direction); err != nil {
+ return err
+ }
+
+ if node.IsLeaf() {
+ return nil
+ }
+
+ err = traverse(node.Left(), node, "l")
+ if err != nil {
+ return err
+ }
+ err = traverse(node.Right(), node, "r")
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+
+ return traverse(nodePtr, nil, "")
+}
+
+var graphvizFillColors = []string{"purple", "green", "red", "blue", "yellow"}
+var graphvizTextColors = []string{"white", "black", "white", "white", "black"}
+
+func RenderNodeDotGraph(writer io.Writer, nodePtr *NodePointer) error {
+ _, err := fmt.Fprintln(writer, "digraph G {\n\trankdir=BT")
+ if err != nil {
+ return err
+ }
+ finishGraph := func() error {
+ _, err := fmt.Fprintln(writer, "}")
+ return err
+ }
+ if nodePtr == nil {
+ return finishGraph()
+ }
+
+ err = DebugTraverseNode(nodePtr, func(node, parent Node, direction string) error {
+ key, err := node.Key()
+ if err != nil {
+ return err
+ }
+
+ version := node.Version()
+ id := node.ID()
+
+ idx := id.Index()
+ label := fmt.Sprintf("ver: %d idx: %d key:0x%x ", version, idx, key)
+ attrs := ""
+ fillColor := graphvizFillColors[version%uint32(len(graphvizFillColors))]
+ textColor := graphvizTextColors[version%uint32(len(graphvizTextColors))]
+ attrs += fmt.Sprintf(" fillcolor=%s fontcolor=%s style=filled", fillColor, textColor)
+ if node.IsLeaf() {
+ value, err := node.Value()
+ if err != nil {
+ return err
+ }
+
+ label += fmt.Sprintf("val:0x%X", value)
+ attrs += " shape=box"
+ } else {
+ label += fmt.Sprintf("ht:%d sz:%d", node.Height(), node.Size())
+ }
+
+ nodeName := graphvizNodeID(id)
+
+ _, err = fmt.Fprintf(writer, "\t%s [id=%s label=\"%s\"%s];\n", nodeName, nodeName, label, attrs)
+ if err != nil {
+ return err
+ }
+ if parent != nil {
+ parentName := graphvizNodeID(parent.ID())
+ _, err = fmt.Fprintf(writer, "\t%s -> %s [label=\"%s\"];\n", parentName, nodeName, direction)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return finishGraph()
+}
+
+func RenderChangesetDotGraph(writer io.Writer, cs *Changeset, orphans map[NodeID]uint32) error {
+ _, err := fmt.Fprintln(writer, "digraph G {\n\trankdir=LR")
+ if err != nil {
+ return err
+ }
+ finishGraph := func() error {
+ _, err := fmt.Fprintln(writer, "}")
+ return err
+ }
+
+ _, err = fmt.Fprintln(writer, "\tsubgraph cluster_branches {\t\nlabel=\"Branches\"")
+ if err != nil {
+ return err
+ }
+
+ numBranches := cs.branchesData.Count()
+ curVersion := uint64(0)
+ var lastBranchId NodeID
+ for i := 0; i < numBranches; i++ {
+ branchLayout := cs.branchesData.UnsafeItem(uint32(i))
+ id := branchLayout.ID()
+ nodeVersion := id.Version()
+ if nodeVersion != curVersion {
+ if curVersion != 0 {
+ _, err = fmt.Fprintln(writer, "\t}")
+ }
+ fillColor := graphvizFillColors[nodeVersion%uint64(len(graphvizFillColors))]
+ textColor := graphvizTextColors[nodeVersion%uint64(len(graphvizTextColors))]
+ _, err = fmt.Fprintf(writer, "\tsubgraph cluster_B%d {\n\t\tlabel=\"Version %d\" color=%s style=filled fontcolor=%s node [fontcolor=%s]\n", nodeVersion, nodeVersion, fillColor, textColor, textColor)
+ }
+ curVersion = nodeVersion
+ if lastBranchId != 0 {
+ _, err = fmt.Fprintf(writer, "\t\t%s -> %s [style=invis];\n", graphvizNodeID(lastBranchId), graphvizNodeID(id))
+ }
+ lastBranchId = id
+
+ nodeName := graphvizNodeID(id)
+ idx := id.Index()
+ label := fmt.Sprintf("idx: %d", idx)
+ orphanVersion, isOrphan := orphans[id]
+ if isOrphan {
+ label += fmt.Sprintf("
orphaned: %d", orphanVersion)
+ }
+ attrs := ""
+ if isOrphan {
+ attrs = " style=dashed"
+ }
+ vi, err := cs.getVersionInfo(uint32(nodeVersion))
+ if err != nil {
+ return err
+ }
+ if vi.RootID == id {
+ attrs += " shape=doublecircle"
+ }
+ _, err = fmt.Fprintf(writer, "\t\t%s [id=%s label=<%s>%s];\n", nodeName, nodeName, label, attrs)
+ if err != nil {
+ return err
+ }
+ }
+ // finish last version subgraph
+ _, err = fmt.Fprintln(writer, "\t}")
+ if err != nil {
+ return err
+ }
+
+ // finish branches subgraph
+ _, err = fmt.Fprintln(writer, "\t}")
+ if err != nil {
+ return err
+ }
+
+ _, err = fmt.Fprintln(writer, "\tsubgraph cluster_leaves {\t\nlabel=\"Leaves\"")
+ if err != nil {
+ return err
+ }
+ numLeaves := cs.leavesData.Count()
+ curVersion = 0
+ var lastLeafId NodeID
+ for i := 0; i < numLeaves; i++ {
+ leafLayout := cs.leavesData.UnsafeItem(uint32(i))
+ id := leafLayout.ID()
+ nodeVersion := id.Version()
+ if nodeVersion != curVersion {
+ if curVersion != 0 {
+ _, err = fmt.Fprintln(writer, "\t}")
+ }
+ fillColor := graphvizFillColors[nodeVersion%uint64(len(graphvizFillColors))]
+ textColor := graphvizTextColors[nodeVersion%uint64(len(graphvizTextColors))]
+ _, err = fmt.Fprintf(writer, "\tsubgraph cluster_L%d {\n\t\tlabel=\"Version %d\" color=%s fontcolor=%s style=filled node [fontcolor=%s]\n", nodeVersion, nodeVersion, fillColor, textColor, textColor)
+ }
+ curVersion = nodeVersion
+ if lastLeafId != 0 {
+ _, err = fmt.Fprintf(writer, "\t\t%s -> %s [style=invis];\n", graphvizNodeID(lastLeafId), graphvizNodeID(id))
+ }
+ lastLeafId = id
+
+ nodeName := graphvizNodeID(id)
+ label := fmt.Sprintf("idx: %d", id.Index())
+ orphanVersion, isOrphan := orphans[id]
+ if isOrphan {
+ label += fmt.Sprintf("
orphaned: %d", orphanVersion)
+ }
+ attrs := ""
+ if isOrphan {
+ attrs = " style=dashed"
+ }
+ _, err = fmt.Fprintf(writer, "\t\t%s [id=%s label=<%s> shape=box%s];\n", nodeName, nodeName, label, attrs)
+ if err != nil {
+ return err
+ }
+ }
+ // finish last version subgraph
+ _, err = fmt.Fprintln(writer, "\t}")
+ if err != nil {
+ return err
+ }
+
+ // finish leaves subgraph
+ _, err = fmt.Fprintln(writer, "\t}")
+ if err != nil {
+ return err
+ }
+
+ return finishGraph()
+}
+
+func graphvizNodeID(node NodeID) string {
+ if node.IsLeaf() {
+ return fmt.Sprintf("L%d_%d", node.Version(), node.Index())
+ } else {
+ return fmt.Sprintf("B%d_%d", node.Version(), node.Index())
+ }
+}
diff --git a/iavlx/immutable_tree.go b/iavlx/immutable_tree.go
new file mode 100644
index 000000000000..77c01c4cb3c8
--- /dev/null
+++ b/iavlx/immutable_tree.go
@@ -0,0 +1,73 @@
+package iavlx
+
+import (
+ io "io"
+
+ storetypes "cosmossdk.io/store/types"
+)
+
+type ImmutableTree struct {
+ root *NodePointer
+}
+
+func NewImmutableTree(root *NodePointer) *ImmutableTree {
+ return &ImmutableTree{
+ root: root,
+ }
+}
+
+func (tree *ImmutableTree) GetStoreType() storetypes.StoreType {
+ return storetypes.StoreTypeIAVL
+}
+
+func (tree *ImmutableTree) CacheWrap() storetypes.CacheWrap {
+ return NewCacheTree(tree)
+}
+
+func (tree *ImmutableTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap {
+ // TODO support tracing
+ return tree.CacheWrap()
+}
+
+func (tree *ImmutableTree) Get(key []byte) []byte {
+ if tree.root == nil {
+ return nil
+ }
+
+ root, err := tree.root.Resolve()
+ if err != nil {
+ panic(err)
+ }
+
+ value, _, err := root.Get(key)
+ if err != nil {
+ panic(err)
+ }
+
+ return value
+}
+
+func (tree *ImmutableTree) Set(key, value []byte) {
+ panic("cannot set in immutable tree")
+}
+
+func (tree *ImmutableTree) Delete(key []byte) {
+ panic("cannot delete from immutable tree")
+}
+
+func (tree *ImmutableTree) Has(key []byte) bool {
+ val := tree.Get(key)
+ return val != nil
+}
+
+func (tree *ImmutableTree) Iterator(start, end []byte) storetypes.Iterator {
+ return NewIterator(start, end, true, tree.root, true)
+}
+
+func (tree *ImmutableTree) ReverseIterator(start, end []byte) storetypes.Iterator {
+ return NewIterator(start, end, false, tree.root, true)
+}
+
+var (
+ _ storetypes.KVStore = (*ImmutableTree)(nil)
+)
diff --git a/iavlx/internal/btree.go b/iavlx/internal/btree.go
new file mode 100644
index 000000000000..8f0b78b197ed
--- /dev/null
+++ b/iavlx/internal/btree.go
@@ -0,0 +1,106 @@
+package internal
+
+import (
+ "bytes"
+ "errors"
+
+ "github.com/tidwall/btree"
+
+ "cosmossdk.io/store/types"
+)
+
+const (
+ // The approximate number of items and children per B-tree node. Tuned with benchmarks.
+ // copied from memdb.
+ bTreeDegree = 32
+)
+
+var errKeyEmpty = errors.New("key cannot be empty")
+
+// BTree implements the sorted cache for cachekv store,
+// we don't use MemDB here because cachekv is used extensively in sdk core path,
+// we need it to be as fast as possible, while `MemDB` is mainly used as a mocking db in unit tests.
+//
+// We choose tidwall/btree over google/btree here because it provides API to implement step iterator directly.
+type BTree struct {
+ tree *btree.BTreeG[item]
+}
+
+// NewBTree creates a wrapper around `btree.BTreeG`.
+func NewBTree() BTree {
+ return BTree{
+ tree: btree.NewBTreeGOptions(byKeys, btree.Options{
+ Degree: bTreeDegree,
+ NoLocks: false,
+ }),
+ }
+}
+
+func (bt BTree) Set(key, value []byte) {
+ bt.tree.Set(newItem(key, value, true))
+}
+
+func (bt BTree) SetCached(key, value []byte) {
+ bt.tree.Set(newItem(key, value, false))
+}
+
+func (bt BTree) Get(key []byte) ([]byte, bool) {
+ i, found := bt.tree.Get(newItem(key, nil, false))
+ if !found {
+ return nil, false
+ }
+ return i.value, true
+}
+
+func (bt BTree) Delete(key []byte) {
+ bt.tree.Set(newItem(key, nil, true))
+}
+
+func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) {
+ if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
+ return nil, errKeyEmpty
+ }
+ return newMemIterator(start, end, bt, true), nil
+}
+
+func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) {
+ if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
+ return nil, errKeyEmpty
+ }
+ return newMemIterator(start, end, bt, false), nil
+}
+
+func (bt BTree) Scan(f func(key []byte, value []byte, dirty bool) bool) {
+ bt.tree.Scan(func(item item) bool {
+ return f(item.key, item.value, item.dirty)
+ })
+}
+
+// Copy the tree. This is a copy-on-write operation and is very fast because
+// it only performs a shadowed copy.
+func (bt BTree) Copy() BTree {
+ return BTree{
+ tree: bt.tree.Copy(),
+ }
+}
+
+func (bt BTree) Clear() {
+ bt.tree.Clear()
+}
+
+// item is a btree item with byte slices as keys and values
+type item struct {
+ key []byte
+ value []byte
+ dirty bool
+}
+
+// byKeys compares the items by key
+func byKeys(a, b item) bool {
+ return bytes.Compare(a.key, b.key) == -1
+}
+
+// newItem creates a new pair item.
+func newItem(key, value []byte, dirty bool) item {
+ return item{key: key, value: value, dirty: dirty}
+}
diff --git a/iavlx/internal/btree_test.go b/iavlx/internal/btree_test.go
new file mode 100644
index 000000000000..50fa74c56a74
--- /dev/null
+++ b/iavlx/internal/btree_test.go
@@ -0,0 +1,204 @@
+package internal
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "cosmossdk.io/store/types"
+)
+
+func TestGetSetDelete(t *testing.T) {
+ db := NewBTree()
+
+ // A nonexistent key should return nil.
+ value, _ := db.Get([]byte("a"))
+ require.Nil(t, value)
+
+ // Set and get a value.
+ db.Set([]byte("a"), []byte{0x01})
+ db.Set([]byte("b"), []byte{0x02})
+ value, _ = db.Get([]byte("a"))
+ require.Equal(t, []byte{0x01}, value)
+
+ value, _ = db.Get([]byte("b"))
+ require.Equal(t, []byte{0x02}, value)
+
+ // Deleting a non-existent value is fine.
+ db.Delete([]byte("x"))
+
+ // Delete a value.
+ db.Delete([]byte("a"))
+
+ value, _ = db.Get([]byte("a"))
+ require.Nil(t, value)
+
+ db.Delete([]byte("b"))
+
+ value, _ = db.Get([]byte("b"))
+ require.Nil(t, value)
+}
+
+func TestDBIterator(t *testing.T) {
+ db := NewBTree()
+
+ for i := 0; i < 10; i++ {
+ if i != 6 { // but skip 6.
+ db.Set(int642Bytes(int64(i)), []byte{})
+ }
+ }
+
+ // Blank iterator keys should error
+ _, err := db.ReverseIterator([]byte{}, nil)
+ require.Equal(t, errKeyEmpty, err)
+ _, err = db.ReverseIterator(nil, []byte{})
+ require.Equal(t, errKeyEmpty, err)
+
+ itr, err := db.Iterator(nil, nil)
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator")
+
+ ritr, err := db.ReverseIterator(nil, nil)
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator")
+
+ itr, err = db.Iterator(nil, int642Bytes(0))
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64(nil), "forward iterator to 0")
+
+ ritr, err = db.ReverseIterator(int642Bytes(10), nil)
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64(nil), "reverse iterator from 10 (ex)")
+
+ itr, err = db.Iterator(int642Bytes(0), nil)
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0")
+
+ itr, err = db.Iterator(int642Bytes(1), nil)
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1")
+
+ ritr, err = db.ReverseIterator(nil, int642Bytes(10))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)")
+
+ ritr, err = db.ReverseIterator(nil, int642Bytes(9))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)")
+
+ ritr, err = db.ReverseIterator(nil, int642Bytes(8))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)")
+
+ itr, err = db.Iterator(int642Bytes(5), int642Bytes(6))
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 6")
+
+ itr, err = db.Iterator(int642Bytes(5), int642Bytes(7))
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 7")
+
+ itr, err = db.Iterator(int642Bytes(5), int642Bytes(8))
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{5, 7}, "forward iterator from 5 to 8")
+
+ itr, err = db.Iterator(int642Bytes(6), int642Bytes(7))
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64(nil), "forward iterator from 6 to 7")
+
+ itr, err = db.Iterator(int642Bytes(6), int642Bytes(8))
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{7}, "forward iterator from 6 to 8")
+
+ itr, err = db.Iterator(int642Bytes(7), int642Bytes(8))
+ require.NoError(t, err)
+ verifyIterator(t, itr, []int64{7}, "forward iterator from 7 to 8")
+
+ ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(5))
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64{4}, "reverse iterator from 5 (ex) to 4")
+
+ ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(6))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64{5, 4}, "reverse iterator from 6 (ex) to 4")
+
+ ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(7))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64{5, 4}, "reverse iterator from 7 (ex) to 4")
+
+ ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(6))
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64{5}, "reverse iterator from 6 (ex) to 5")
+
+ ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(7))
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64{5}, "reverse iterator from 7 (ex) to 5")
+
+ ritr, err = db.ReverseIterator(int642Bytes(6), int642Bytes(7))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64(nil), "reverse iterator from 7 (ex) to 6")
+
+ ritr, err = db.ReverseIterator(int642Bytes(10), nil)
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64(nil), "reverse iterator to 10")
+
+ ritr, err = db.ReverseIterator(int642Bytes(6), nil)
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64{9, 8, 7}, "reverse iterator to 6")
+
+ ritr, err = db.ReverseIterator(int642Bytes(5), nil)
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64{9, 8, 7, 5}, "reverse iterator to 5")
+
+ ritr, err = db.ReverseIterator(int642Bytes(8), int642Bytes(9))
+ require.NoError(t, err)
+ verifyIterator(t, ritr, []int64{8}, "reverse iterator from 9 (ex) to 8")
+
+ ritr, err = db.ReverseIterator(int642Bytes(2), int642Bytes(4))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64{3, 2}, "reverse iterator from 4 (ex) to 2")
+
+ ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(2))
+ require.NoError(t, err)
+ verifyIterator(t, ritr,
+ []int64(nil), "reverse iterator from 2 (ex) to 4")
+
+ // Ensure that the iterators don't panic with an empty database.
+ db2 := NewBTree()
+
+ itr, err = db2.Iterator(nil, nil)
+ require.NoError(t, err)
+ verifyIterator(t, itr, nil, "forward iterator with empty db")
+
+ ritr, err = db2.ReverseIterator(nil, nil)
+ require.NoError(t, err)
+ verifyIterator(t, ritr, nil, "reverse iterator with empty db")
+}
+
+func verifyIterator(t *testing.T, itr types.Iterator, expected []int64, msg string) {
+ t.Helper()
+ i := 0
+ for itr.Valid() {
+ key := itr.Key()
+ require.Equal(t, expected[i], bytes2Int64(key), "iterator: %d mismatches", i)
+ itr.Next()
+ i++
+ }
+ require.Equal(t, i, len(expected), "expected to have fully iterated over all the elements in iter")
+ require.NoError(t, itr.Close())
+}
+
+func int642Bytes(i int64) []byte {
+ return types.Uint64ToBigEndian(uint64(i))
+}
+
+func bytes2Int64(buf []byte) int64 {
+ return int64(types.BigEndianToUint64(buf))
+}
diff --git a/iavlx/internal/memiterator.go b/iavlx/internal/memiterator.go
new file mode 100644
index 000000000000..ab7e6e13a6ba
--- /dev/null
+++ b/iavlx/internal/memiterator.go
@@ -0,0 +1,120 @@
+package internal
+
+import (
+ "bytes"
+ "errors"
+
+ "github.com/tidwall/btree"
+
+ "cosmossdk.io/store/types"
+)
+
+var _ types.Iterator = (*memIterator)(nil)
+
+// memIterator iterates over iterKVCache items.
+// if value is nil, means it was deleted.
+// Implements Iterator.
+type memIterator struct {
+ iter btree.IterG[item]
+
+ start []byte
+ end []byte
+ ascending bool
+ valid bool
+}
+
+func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator {
+ iter := items.tree.Iter()
+ var valid bool
+ if ascending {
+ if start != nil {
+ valid = iter.Seek(newItem(start, nil, false))
+ } else {
+ valid = iter.First()
+ }
+ } else {
+ if end != nil {
+ valid = iter.Seek(newItem(end, nil, false))
+ if !valid {
+ valid = iter.Last()
+ } else {
+ // end is exclusive
+ valid = iter.Prev()
+ }
+ } else {
+ valid = iter.Last()
+ }
+ }
+
+ mi := &memIterator{
+ iter: iter,
+ start: start,
+ end: end,
+ ascending: ascending,
+ valid: valid,
+ }
+
+ if mi.valid {
+ mi.valid = mi.keyInRange(mi.Key())
+ }
+
+ return mi
+}
+
+func (mi *memIterator) Domain() (start, end []byte) {
+ return mi.start, mi.end
+}
+
+func (mi *memIterator) Close() error {
+ mi.iter.Release()
+ return nil
+}
+
+func (mi *memIterator) Error() error {
+ if !mi.Valid() {
+ return errors.New("invalid memIterator")
+ }
+ return nil
+}
+
+func (mi *memIterator) Valid() bool {
+ return mi.valid
+}
+
+func (mi *memIterator) Next() {
+ mi.assertValid()
+
+ if mi.ascending {
+ mi.valid = mi.iter.Next()
+ } else {
+ mi.valid = mi.iter.Prev()
+ }
+
+ if mi.valid {
+ mi.valid = mi.keyInRange(mi.Key())
+ }
+}
+
+func (mi *memIterator) keyInRange(key []byte) bool {
+ if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 {
+ return false
+ }
+ if !mi.ascending && mi.start != nil && bytes.Compare(key, mi.start) < 0 {
+ return false
+ }
+ return true
+}
+
+func (mi *memIterator) Key() []byte {
+ return mi.iter.Item().key
+}
+
+func (mi *memIterator) Value() []byte {
+ return mi.iter.Item().value
+}
+
+func (mi *memIterator) assertValid() {
+ if err := mi.Error(); err != nil {
+ panic(err)
+ }
+}
diff --git a/iavlx/internal/mergeiterator.go b/iavlx/internal/mergeiterator.go
new file mode 100644
index 000000000000..15b215841931
--- /dev/null
+++ b/iavlx/internal/mergeiterator.go
@@ -0,0 +1,235 @@
+package internal
+
+import (
+ "bytes"
+ "errors"
+
+ "cosmossdk.io/store/types"
+)
+
+// cacheMergeIterator merges a parent Iterator and a cache Iterator.
+// The cache iterator may return nil keys to signal that an item
+// had been deleted (but not deleted in the parent).
+// If the cache iterator has the same key as the parent, the
+// cache shadows (overrides) the parent.
+//
+// TODO: Optimize by memoizing.
+type cacheMergeIterator struct {
+ parent types.Iterator
+ cache types.Iterator
+ ascending bool
+
+ valid bool
+}
+
+var _ types.Iterator = (*cacheMergeIterator)(nil)
+
+func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) types.Iterator {
+ iter := &cacheMergeIterator{
+ parent: parent,
+ cache: cache,
+ ascending: ascending,
+ }
+
+ iter.valid = iter.skipUntilExistsOrInvalid()
+ return iter
+}
+
+// Domain implements Iterator.
+// Returns parent domain because cache and parent domains are the same.
+func (iter *cacheMergeIterator) Domain() (start, end []byte) {
+ return iter.parent.Domain()
+}
+
+// Valid implements Iterator.
+func (iter *cacheMergeIterator) Valid() bool {
+ return iter.valid
+}
+
+// Next implements Iterator
+func (iter *cacheMergeIterator) Next() {
+ iter.assertValid()
+
+ switch {
+ case !iter.parent.Valid():
+ // If parent is invalid, get the next cache item.
+ iter.cache.Next()
+ case !iter.cache.Valid():
+ // If cache is invalid, get the next parent item.
+ iter.parent.Next()
+ default:
+ // Both are valid. Compare keys.
+ keyP, keyC := iter.parent.Key(), iter.cache.Key()
+ switch iter.compare(keyP, keyC) {
+ case -1: // parent < cache
+ iter.parent.Next()
+ case 0: // parent == cache
+ iter.parent.Next()
+ iter.cache.Next()
+ case 1: // parent > cache
+ iter.cache.Next()
+ }
+ }
+ iter.valid = iter.skipUntilExistsOrInvalid()
+}
+
+// Key implements Iterator
+func (iter *cacheMergeIterator) Key() []byte {
+ iter.assertValid()
+
+ // If parent is invalid, get the cache key.
+ if !iter.parent.Valid() {
+ return iter.cache.Key()
+ }
+
+ // If cache is invalid, get the parent key.
+ if !iter.cache.Valid() {
+ return iter.parent.Key()
+ }
+
+ // Both are valid. Compare keys.
+ keyP, keyC := iter.parent.Key(), iter.cache.Key()
+
+ cmp := iter.compare(keyP, keyC)
+ switch cmp {
+ case -1: // parent < cache
+ return keyP
+ case 0: // parent == cache
+ return keyP
+ case 1: // parent > cache
+ return keyC
+ default:
+ panic("invalid compare result")
+ }
+}
+
+// Value implements Iterator
+func (iter *cacheMergeIterator) Value() []byte {
+ iter.assertValid()
+
+ // If parent is invalid, get the cache value.
+ if !iter.parent.Valid() {
+ return iter.cache.Value()
+ }
+
+ // If cache is invalid, get the parent value.
+ if !iter.cache.Valid() {
+ return iter.parent.Value()
+ }
+
+ // Both are valid. Compare keys.
+ keyP, keyC := iter.parent.Key(), iter.cache.Key()
+
+ cmp := iter.compare(keyP, keyC)
+ switch cmp {
+ case -1: // parent < cache
+ return iter.parent.Value()
+ case 0: // parent == cache
+ return iter.cache.Value()
+ case 1: // parent > cache
+ return iter.cache.Value()
+ default:
+ panic("invalid comparison result")
+ }
+}
+
+// Close implements Iterator
+func (iter *cacheMergeIterator) Close() error {
+ err1 := iter.cache.Close()
+ if err := iter.parent.Close(); err != nil {
+ return err
+ }
+
+ return err1
+}
+
+// Error returns an error if the cacheMergeIterator is invalid defined by the
+// Valid method.
+func (iter *cacheMergeIterator) Error() error {
+ if !iter.Valid() {
+ return errors.New("invalid cacheMergeIterator")
+ }
+
+ return nil
+}
+
+// assertValid checks if not valid, panics.
+// NOTE: May have side-effect of iterating over cache.
+func (iter *cacheMergeIterator) assertValid() {
+ if err := iter.Error(); err != nil {
+ panic(err)
+ }
+}
+
+// compare is like bytes.Compare but opposite if not ascending.
+func (iter *cacheMergeIterator) compare(a, b []byte) int {
+ if iter.ascending {
+ return bytes.Compare(a, b)
+ }
+
+ return bytes.Compare(a, b) * -1
+}
+
+// skipCacheDeletes skips all delete-items from the cache w/ `key < until`. After this function,
+// current cache item is a non-delete-item, or `until <= key`.
+// If the current cache item is not a delete item, does nothing.
+// If `until` is nil, there is no limit, and cache may end up invalid.
+// CONTRACT: cache is valid.
+func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) {
+ for iter.cache.Valid() &&
+ iter.cache.Value() == nil &&
+ (until == nil || iter.compare(iter.cache.Key(), until) < 0) {
+ iter.cache.Next()
+ }
+}
+
+// skipUntilExistsOrInvalid fast forwards cache (or parent+cache in case of deleted items) until current
+// item exists, or until iterator becomes invalid.
+// Returns whether the iterator is valid.
+func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool {
+ for {
+ // If parent is invalid, fast-forward cache.
+ if !iter.parent.Valid() {
+ iter.skipCacheDeletes(nil)
+ return iter.cache.Valid()
+ }
+ // Parent is valid.
+
+ if !iter.cache.Valid() {
+ return true
+ }
+ // Parent is valid, cache is valid.
+
+ // Compare parent and cache.
+ keyP := iter.parent.Key()
+ keyC := iter.cache.Key()
+
+ switch iter.compare(keyP, keyC) {
+ case -1: // parent < cache.
+ return true
+
+ case 0: // parent == cache.
+ // Skip over if cache item is a delete.
+ valueC := iter.cache.Value()
+ if valueC == nil {
+ iter.parent.Next()
+ iter.cache.Next()
+
+ continue
+ }
+ // Cache is not a delete.
+
+ return true // cache exists.
+ case 1: // cache < parent
+ // Skip over if cache item is a delete.
+ valueC := iter.cache.Value()
+ if valueC == nil {
+ iter.skipCacheDeletes(keyP)
+ continue
+ }
+ // Cache is not a delete.
+
+ return true // cache exists.
+ }
+ }
+}
diff --git a/iavlx/iterator.go b/iavlx/iterator.go
new file mode 100644
index 000000000000..111c0f9c9352
--- /dev/null
+++ b/iavlx/iterator.go
@@ -0,0 +1,139 @@
+package iavlx
+
+import "bytes"
+
+type Iterator struct {
+ // domain of iteration, end is exclusive
+ start, end []byte
+ ascending bool
+ zeroCopy bool
+
+ // cache the next key-value pair
+ key, value []byte
+
+ err error
+ valid bool
+
+ stack []*NodePointer
+}
+
+func NewIterator(start, end []byte, ascending bool, root *NodePointer, zeroCopy bool) *Iterator {
+ iter := &Iterator{
+ start: start,
+ end: end,
+ ascending: ascending,
+ valid: true,
+ zeroCopy: zeroCopy,
+ }
+
+ if root != nil {
+ iter.stack = []*NodePointer{root}
+ }
+
+ // cache the first key-value
+ iter.Next()
+ return iter
+}
+
+func (iter *Iterator) Domain() ([]byte, []byte) {
+ return iter.start, iter.end
+}
+
+// Valid implements dbm.Iterator.
+func (iter *Iterator) Valid() bool {
+ return iter.valid
+}
+
+// Error implements dbm.Iterator
+func (iter *Iterator) Error() error {
+ return nil
+}
+
+// Key implements dbm.Iterator
+func (iter *Iterator) Key() []byte {
+ if !iter.zeroCopy {
+ return bytes.Clone(iter.key)
+ }
+ return iter.key
+}
+
+// Value implements dbm.Iterator
+func (iter *Iterator) Value() []byte {
+ if !iter.zeroCopy {
+ return bytes.Clone(iter.value)
+ }
+ return iter.value
+}
+
+// Next implements dbm.Iterator
+func (iter *Iterator) Next() {
+ if !iter.valid {
+ return
+ }
+
+ for len(iter.stack) > 0 {
+ // pop node
+ nodePtr := iter.stack[len(iter.stack)-1]
+ iter.stack = iter.stack[:len(iter.stack)-1]
+
+ node, err := nodePtr.Resolve()
+ if err != nil {
+ iter.fail(err)
+ return
+ }
+
+ key, err := node.Key()
+ if err != nil {
+ iter.fail(err)
+ return
+ }
+ startCmp := bytes.Compare(iter.start, key)
+ afterStart := iter.start == nil || startCmp < 0
+ beforeEnd := iter.end == nil || bytes.Compare(key, iter.end) < 0
+
+ if node.IsLeaf() {
+ startOrAfter := afterStart || startCmp == 0
+ if startOrAfter && beforeEnd {
+ iter.key = key
+ value, err := node.Value()
+ if err != nil {
+ iter.fail(err)
+ return
+ }
+ iter.value = value
+ return
+ }
+ } else {
+ // push children to stack
+ if iter.ascending {
+ if beforeEnd {
+ iter.stack = append(iter.stack, node.Right())
+ }
+ if afterStart {
+ iter.stack = append(iter.stack, node.Left())
+ }
+ } else {
+ if afterStart {
+ iter.stack = append(iter.stack, node.Left())
+ }
+ if beforeEnd {
+ iter.stack = append(iter.stack, node.Right())
+ }
+ }
+ }
+ }
+
+ iter.valid = false
+}
+
+func (iter *Iterator) fail(err error) {
+ iter.valid = false
+ iter.err = err
+}
+
+// Close implements dbm.Iterator
+func (iter *Iterator) Close() error {
+ iter.valid = false
+ iter.stack = nil
+ return nil
+}
diff --git a/iavlx/kvlog.go b/iavlx/kvlog.go
new file mode 100644
index 000000000000..afdaa89ae7fe
--- /dev/null
+++ b/iavlx/kvlog.go
@@ -0,0 +1,51 @@
+package iavlx
+
+import (
+ "encoding/binary"
+ "os"
+)
+
+const (
+ KVLogEntryTypeSet byte = iota
+ KVLogEntryTypeDelete
+ KVLogEntryTypeCommit
+ KVLogEntryTypeExtraK
+ KVLogEntryTypeExtraKV
+)
+
+type KVLog struct {
+ *MmapFile
+}
+
+func NewKVLog(file *os.File) (*KVLog, error) {
+ mmap, err := NewMmapFile(file)
+ if err != nil {
+ return nil, err
+ }
+ return &KVLog{
+ MmapFile: mmap,
+ }, nil
+}
+
+func (kvs *KVLog) UnsafeReadK(offset uint32) (key []byte, err error) {
+ bz, err := kvs.UnsafeSliceExact(int(offset), 4)
+ if err != nil {
+ return nil, err
+ }
+ lenKey := binary.LittleEndian.Uint32(bz)
+
+ return kvs.UnsafeSliceExact(int(offset)+4, int(lenKey))
+}
+
+func (kvs *KVLog) UnsafeReadKV(offset uint32) (key, value []byte, err error) {
+ key, err = kvs.UnsafeReadK(offset)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ value, err = kvs.UnsafeReadK(offset + 4 + uint32(len(key)))
+ if err != nil {
+ return nil, nil, err
+ }
+ return key, value, nil
+}
diff --git a/iavlx/kvlog_writer.go b/iavlx/kvlog_writer.go
new file mode 100644
index 000000000000..19bb3083634d
--- /dev/null
+++ b/iavlx/kvlog_writer.go
@@ -0,0 +1,114 @@
+package iavlx
+
+import (
+ "encoding/binary"
+ "fmt"
+ "math"
+ "os"
+)
+
+type KVLogWriter struct {
+ *FileWriter
+}
+
+func NewKVDataWriter(file *os.File) *KVLogWriter {
+ fw := NewFileWriter(file)
+ return &KVLogWriter{
+ FileWriter: fw,
+ }
+}
+
+func (kvs *KVLogWriter) WriteK(key []byte) (offset uint32, err error) {
+ _, err = kvs.Write([]byte{KVLogEntryTypeExtraK})
+ if err != nil {
+ return offset, err
+ }
+
+ return kvs.writeLenPrefixedBytes(key)
+}
+
+func (kvs *KVLogWriter) WriteKV(key, value []byte) (offset uint32, err error) {
+ _, err = kvs.Write([]byte{KVLogEntryTypeExtraKV})
+ if err != nil {
+ return offset, err
+ }
+
+ offset, err = kvs.writeLenPrefixedBytes(key)
+ if err != nil {
+ return 0, err
+ }
+ _, err = kvs.writeLenPrefixedBytes(value)
+ return offset, err
+}
+
+func (kvs *KVLogWriter) WriteUpdates(updates []KVUpdate) error {
+ for _, update := range updates {
+ if deleteKey := update.DeleteKey; deleteKey != nil {
+ _, err := kvs.Write([]byte{KVLogEntryTypeDelete})
+ if err != nil {
+ return err
+ }
+ _, err = kvs.writeLenPrefixedBytes(deleteKey)
+ if err != nil {
+ return err
+ }
+ } else if memNode := update.SetNode; memNode != nil {
+ _, err := kvs.Write([]byte{KVLogEntryTypeSet})
+ if err != nil {
+ return err
+ }
+ offset, err := kvs.writeLenPrefixedBytes(memNode.key)
+ if err != nil {
+ return err
+ }
+ memNode.keyOffset = offset
+
+ _, err = kvs.writeLenPrefixedBytes(memNode.value)
+ if err != nil {
+ return err
+ }
+ } else {
+ return fmt.Errorf("invalid update: neither SetNode nor DeleteKey is set")
+ }
+ }
+ return nil
+}
+
+func (kvs *KVLogWriter) WriteCommit(version uint32) error {
+ _, err := kvs.Write([]byte{KVLogEntryTypeCommit})
+ if err != nil {
+ return err
+ }
+
+ return kvs.writeLEU32(version)
+}
+
+func (kvs *KVLogWriter) writeLenPrefixedBytes(key []byte) (offset uint32, err error) {
+ lenKey := len(key)
+ if lenKey > math.MaxUint32 {
+ return 0, fmt.Errorf("key too large: %d bytes", lenKey)
+ }
+
+ offset = uint32(kvs.Size())
+
+ // write little endian uint32 length prefix
+ err = kvs.writeLEU32(uint32(lenKey))
+ if err != nil {
+ return offset, err
+ }
+
+ // write key bytes
+ _, err = kvs.Write(key)
+ if err != nil {
+ return offset, err
+ }
+
+ return offset, nil
+}
+
+func (kvs *KVLogWriter) writeLEU32(x uint32) error {
+ var buf [4]byte
+ binary.LittleEndian.PutUint32(buf[:], x)
+ _, err := kvs.Write(buf[:])
+ return err
+}
diff --git a/iavlx/leaf_layout.go b/iavlx/leaf_layout.go
new file mode 100644
index 000000000000..11f90311e1d5
--- /dev/null
+++ b/iavlx/leaf_layout.go
@@ -0,0 +1,26 @@
+package iavlx
+
+import (
+ "fmt"
+ "unsafe"
+)
+
+func init() {
+ if unsafe.Sizeof(LeafLayout{}) != SizeLeaf {
+ panic(fmt.Sprintf("invalid LeafLayout size: got %d, want %d", unsafe.Sizeof(LeafLayout{}), SizeLeaf))
+ }
+}
+
+const (
+ SizeLeaf = 48
+)
+
+type LeafLayout struct {
+ Id NodeID
+ Hash [32]byte
+ KeyOffset uint32 // TODO check if we have extra padding here
+}
+
+func (l LeafLayout) ID() NodeID {
+ return l.Id
+}
diff --git a/iavlx/leaf_persisted.go b/iavlx/leaf_persisted.go
new file mode 100644
index 000000000000..8ebb1a5f9385
--- /dev/null
+++ b/iavlx/leaf_persisted.go
@@ -0,0 +1,87 @@
+package iavlx
+
+import (
+ "bytes"
+ "fmt"
+)
+
+type LeafPersisted struct {
+ store *Changeset
+ selfIdx uint32
+ layout LeafLayout
+}
+
+func (node *LeafPersisted) ID() NodeID {
+ return node.layout.Id
+}
+
+func (node *LeafPersisted) Height() uint8 {
+ return 0
+}
+
+func (node *LeafPersisted) IsLeaf() bool {
+ return true
+}
+
+func (node *LeafPersisted) Size() int64 {
+ return 1
+}
+
+func (node *LeafPersisted) Version() uint32 {
+ return uint32(node.layout.Id.Version())
+}
+
+func (node *LeafPersisted) Key() ([]byte, error) {
+ return node.store.ReadK(node.layout.Id, node.layout.KeyOffset)
+}
+
+func (node *LeafPersisted) Value() ([]byte, error) {
+ return node.store.ReadV(node.layout.Id, node.layout.KeyOffset)
+}
+
+func (node *LeafPersisted) Left() *NodePointer {
+ return nil
+}
+
+func (node *LeafPersisted) Right() *NodePointer {
+ return nil
+}
+
+func (node *LeafPersisted) Hash() []byte {
+ return node.layout.Hash[:]
+}
+
+func (node *LeafPersisted) SafeHash() []byte {
+ // TODO how do we make this safe?
+ return node.layout.Hash[:]
+}
+
+func (node *LeafPersisted) MutateBranch(uint32) (*MemNode, error) {
+ return nil, fmt.Errorf("leaf nodes should not get mutated this way")
+}
+
+func (node *LeafPersisted) Get(key []byte) (value []byte, index int64, err error) {
+ nodeKey, err := node.Key()
+ if err != nil {
+ return nil, 0, err
+ }
+ switch bytes.Compare(nodeKey, key) {
+ case -1:
+ return nil, 1, nil
+ case 1:
+ return nil, 0, nil
+ default:
+ value, err := node.Value()
+ if err != nil {
+ return nil, 0, err
+ }
+ return value, 0, nil
+ }
+}
+
+func (node *LeafPersisted) String() string {
+ // TODO implement me
+ panic("implement me")
+}
+
+var _ Node = (*LeafPersisted)(nil)
diff --git a/iavlx/load.go b/iavlx/load.go
new file mode 100644
index 000000000000..6adc19d016d7
--- /dev/null
+++ b/iavlx/load.go
@@ -0,0 +1,111 @@
+package iavlx
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/tidwall/btree"
+)
+
+func (ts *TreeStore) load() error {
+ // collect a list of all existing subdirectories
+ dirs, err := os.ReadDir(ts.dir)
+ if err != nil {
+ return fmt.Errorf("failed to read tree store dir: %w", err)
+ }
+
+ // directory map: startVersion -> compactedAt -> dirName
+ var dirMap btree.Map[uint64, *btree.Map[uint64, string]]
+ for _, de := range dirs {
+ if !de.IsDir() {
+ continue
+ }
+
+ dirName := de.Name()
+ startVersion, compactedAt, valid := ParseChangesetDirName(dirName)
+ if !valid {
+ continue
+ }
+
+ dir := filepath.Join(ts.dir, dirName)
+ if _, found := dirMap.Get(startVersion); !found {
+ dirMap.Set(startVersion, &btree.Map[uint64, string]{})
+ }
+ caMap, _ := dirMap.Get(startVersion)
+ caMap.Set(compactedAt, dir)
+ }
+
+ // load changesets in order
+ for {
+ startVersion, compactionMap, ok := dirMap.PopMin()
+ if !ok {
+ return nil
+ }
+
+ // startVersion should be equal to stagedVersion
+
+ if startVersion < uint64(ts.stagedVersion) {
+ ts.logger.Warn("found undeleted changeset that was already compacted", "startVersion", startVersion, "stagedVersion", ts.stagedVersion)
+ // TODO delete undeleted compactions
+ continue
+ }
+
+ if startVersion > uint64(ts.stagedVersion) {
+ return fmt.Errorf("missing changeset for staged version %d", ts.stagedVersion)
+ }
+
+ for {
+ _, dirName, ok := compactionMap.PopMax()
+ if !ok {
+ return fmt.Errorf("internal error: no changeset entries for start version %d", startVersion)
+ }
+
+ ready, err := IsChangesetReady(dirName)
+ if err != nil {
+ return fmt.Errorf("failed to check if changeset %s is ready: %w", dirName, err)
+ }
+
+ if !ready {
+ ts.logger.Warn("found incomplete compaction, deleting", "dir", dirName)
+ err := os.RemoveAll(dirName)
+ if err != nil {
+ ts.logger.Error("failed to remove incomplete compaction", "dir", dirName, "error", err)
+ }
+ continue
+ }
+
+ ts.logger.Debug("loading changeset", "startVersion", startVersion, "dir", dirName)
+
+ cs, err := OpenChangeset(ts, dirName)
+ if err != nil {
+ return fmt.Errorf("failed to open changeset in %s: %w", dirName, err)
+ }
+
+ realStartVersion := cs.info.StartVersion
+ if uint64(realStartVersion) != startVersion {
+ if realStartVersion == 0 {
+ if dirMap.Len() != 0 {
+ return fmt.Errorf("found incomplete changeset %s, but there are later changesets present", dirName)
+ }
+ ts.logger.Debug("found final incomplete changeset, deleting", "dir", dirName)
+ err := os.RemoveAll(dirName)
+ if err != nil {
+ return fmt.Errorf("failed to remove incomplete changeset %s: %w", dirName, err)
+ }
+ break
+ } else {
+ return fmt.Errorf("changeset in %s has mismatched start version %d (expected %d)", dirName, realStartVersion, startVersion)
+ }
+ }
+
+ ce := &changesetEntry{}
+ ce.changeset.Store(cs)
+ ts.changesets.Set(uint32(startVersion), ce)
+
+ ts.savedVersion.Store(cs.info.EndVersion)
+ ts.stagedVersion = cs.info.EndVersion + 1
+ break
+ }
+ }
+}
diff --git a/iavlx/mem_node.go b/iavlx/mem_node.go
new file mode 100644
index 000000000000..a8bb4a0168db
--- /dev/null
+++ b/iavlx/mem_node.go
@@ -0,0 +1,116 @@
+package iavlx
+
+import (
+ "bytes"
+ "fmt"
+)
+
+type MemNode struct {
+ height uint8
+ size int64
+ version uint32
+ key []byte
+ value []byte
+ left *NodePointer
+ right *NodePointer
+ hash []byte
+ nodeId NodeID // ID of this node, 0 if not yet assigned
+ keyOffset uint32
+}
+
+func (node *MemNode) ID() NodeID {
+ return node.nodeId
+}
+
+func (node *MemNode) Height() uint8 {
+ return node.height
+}
+
+func (node *MemNode) Size() int64 {
+ return node.size
+}
+
+func (node *MemNode) Version() uint32 {
+ return node.version
+}
+
+func (node *MemNode) Key() ([]byte, error) {
+ return node.key, nil
+}
+
+func (node *MemNode) Value() ([]byte, error) {
+ return node.value, nil
+}
+
+func (node *MemNode) Left() *NodePointer {
+ return node.left
+}
+
+func (node *MemNode) Right() *NodePointer {
+ return node.right
+}
+
+func (node *MemNode) Hash() []byte {
+ return node.hash
+}
+
+func (node *MemNode) SafeHash() []byte {
+ // TODO what needs to be safe??
+ return node.hash
+}
+
+func (node *MemNode) MutateBranch(version uint32) (*MemNode, error) {
+ n := *node
+ n.version = version
+ n.hash = nil
+ return &n, nil
+}
+
+func (node *MemNode) Get(key []byte) (value []byte, index int64, err error) {
+ if node.IsLeaf() {
+ switch bytes.Compare(node.key, key) {
+ case -1:
+ return nil, 1, nil
+ case 1:
+ return nil, 0, nil
+ default:
+ return node.value, 0, nil
+ }
+ }
+
+ if bytes.Compare(key, node.key) < 0 {
+ leftNode, err := node.left.Resolve()
+ if err != nil {
+ return nil, 0, err
+ }
+
+ return leftNode.Get(key)
+ }
+
+ rightNode, err := node.right.Resolve()
+ if err != nil {
+ return nil, 0, err
+ }
+
+ value, index, err = rightNode.Get(key)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ index += node.size - rightNode.Size()
+ return value, index, nil
+}
+
+func (node *MemNode) IsLeaf() bool {
+ return node.height == 0
+}
+
+func (node *MemNode) String() string {
+ if node.IsLeaf() {
+ return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, value:%x}", node.key, node.version, node.size, node.value)
+ } else {
+ return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, height:%d, left:%s, right:%s}", node.key, node.version, node.size, node.height, node.left, node.right)
+ }
+}
+
+var _ Node = &MemNode{}
diff --git a/iavlx/mmap.go b/iavlx/mmap.go
new file mode 100644
index 000000000000..570538e0f1ae
--- /dev/null
+++ b/iavlx/mmap.go
@@ -0,0 +1,78 @@
+package iavlx
+
+import (
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/edsrzf/mmap-go"
+)
+
+type MmapFile struct {
+ handle mmap.MMap
+}
+
+func NewMmapFile(file *os.File) (*MmapFile, error) {
+ // Check file size
+ fi, err := file.Stat()
+ if err != nil {
+ _ = file.Close()
+ return nil, fmt.Errorf("failed to stat file: %w", err)
+ }
+
+ res := &MmapFile{}
+
+ // Empty files are valid - just don't mmap them
+ if fi.Size() == 0 {
+ return res, nil
+ }
+
+ // maybe we can make read/write configurable? not sure if the OS optimizes read-only mapping
+ handle, err := mmap.Map(file, mmap.RDONLY, 0)
+ if err != nil {
+ _ = file.Close()
+ return nil, fmt.Errorf("failed to mmap file: %w", err)
+ }
+
+ res.handle = handle
+ return res, nil
+}
+
+func (m *MmapFile) UnsafeSliceVar(offset, maxSize int) (int, []byte, error) {
+ if offset >= len(m.handle) {
+ return 0, nil, fmt.Errorf("trying to read beyond mapped data: %d >= %d", offset, len(m.handle))
+ }
+ if offset+maxSize > len(m.handle) {
+ maxSize = len(m.handle) - offset
+ }
+ data := m.handle[offset : offset+maxSize]
+ // make a copy of the data to avoid data being changed after remap
+ return maxSize, data, nil
+}
+
+func (m *MmapFile) UnsafeSliceExact(offset, size int) ([]byte, error) {
+ if offset+size > len(m.handle) {
+ return nil, fmt.Errorf("trying to read beyond mapped data: %d + %d >= %d", offset, size, len(m.handle))
+ }
+ bz := m.handle[offset : offset+size]
+ return bz, nil
+}
+
+func (m *MmapFile) Data() []byte {
+ return m.handle
+}
+
+func (m *MmapFile) Close() error {
+ if m.handle != nil {
+ handle := m.handle
+ m.handle = nil
+ return handle.Unmap()
+ }
+ return nil
+}
+
+func (m *MmapFile) TotalBytes() int {
+ return len(m.handle)
+}
+
+var _ io.Closer = &MmapFile{}
diff --git a/iavlx/multi_tree.go b/iavlx/multi_tree.go
new file mode 100644
index 000000000000..46c3465beabb
--- /dev/null
+++ b/iavlx/multi_tree.go
@@ -0,0 +1,129 @@
+package iavlx
+
+import (
+ "fmt"
+ io "io"
+ "sync"
+
+ storetypes "cosmossdk.io/store/types"
+)
+
+type MultiTree struct {
+ latestVersion int64
+ trees []storetypes.CacheWrap // always ordered by tree name
+ treesByKey map[storetypes.StoreKey]int // index of the trees by name
+
+ parentTree func(storetypes.StoreKey) storetypes.CacheWrapper
+}
+
+func (t *MultiTree) GetObjKVStore(key storetypes.StoreKey) storetypes.ObjKVStore {
+ store, ok := t.getCacheWrapper(key).(storetypes.ObjKVStore)
+ if !ok {
+ panic(fmt.Sprintf("store with key %v is not ObjKVStore", key))
+ }
+ return store
+}
+
+func (t *MultiTree) getCacheWrapper(key storetypes.StoreKey) storetypes.CacheWrapper {
+ var store storetypes.CacheWrapper
+ treeIdx, ok := t.treesByKey[key]
+ if !ok {
+ if t.parentTree != nil {
+ store = t.initStore(key, t.parentTree(key))
+ } else {
+ panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))
+ }
+ } else {
+ store = t.trees[treeIdx]
+ }
+
+ return store
+}
+
+func (t *MultiTree) initStore(key storetypes.StoreKey, store storetypes.CacheWrapper) storetypes.CacheWrap {
+ cache := store.CacheWrap()
+ t.trees = append(t.trees, cache)
+ t.treesByKey[key] = len(t.trees) - 1
+ return cache
+}
+
+func (t *MultiTree) Write() {
+ var wg sync.WaitGroup
+ for _, tree := range t.trees {
+ // TODO check if trees are dirty before spinning off a goroutine
+ wg.Add(1)
+ go func(t storetypes.CacheWrap) {
+ defer wg.Done()
+ t.Write()
+ }(tree)
+ }
+ wg.Wait()
+}
+
+func (t *MultiTree) GetStoreType() storetypes.StoreType {
+ return storetypes.StoreTypeMulti
+}
+
+func (t *MultiTree) CacheWrap() storetypes.CacheWrap {
+ return t.CacheMultiStore()
+}
+
+func (t *MultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap {
+ // TODO implement tracing
+ return t.CacheWrap()
+}
+
+func (t *MultiTree) CacheMultiStore() storetypes.CacheMultiStore {
+ return NewFromParent(t.getCacheWrapper, t.latestVersion)
+}
+
+func NewFromParent(parentStore func(storetypes.StoreKey) storetypes.CacheWrapper, version int64) *MultiTree {
+ return &MultiTree{
+ latestVersion: version,
+ parentTree: parentStore,
+ trees: make([]storetypes.CacheWrap, 0),
+ treesByKey: make(map[storetypes.StoreKey]int),
+ }
+}
+
+func (t *MultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) {
+ return nil, fmt.Errorf("CacheMultiStoreWithVersion can only be called on CommitMultiStore")
+}
+
+func (t *MultiTree) GetStore(key storetypes.StoreKey) storetypes.Store {
+ store, ok := t.getCacheWrapper(key).(storetypes.Store)
+ if !ok {
+ panic(fmt.Sprintf("store with key %v is not Store", key))
+ }
+ return store
+}
+
+func (t *MultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore {
+ store := t.getCacheWrapper(key)
+
+ kvStore, ok := store.(storetypes.KVStore)
+ if !ok {
+ panic(fmt.Sprintf("store with key %v is not KVStore: store type=%T", key, store))
+ }
+ return kvStore
+}
+
+func (t *MultiTree) TracingEnabled() bool {
+ return false
+}
+
+func (t *MultiTree) SetTracer(w io.Writer) storetypes.MultiStore {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (t *MultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (t *MultiTree) LatestVersion() int64 {
+ return t.latestVersion
+}
+
+var _ storetypes.MultiStore = &MultiTree{}
diff --git a/iavlx/node.go b/iavlx/node.go
new file mode 100644
index 000000000000..721d2a6f22b8
--- /dev/null
+++ b/iavlx/node.go
@@ -0,0 +1,21 @@
+package iavlx
+
+import "fmt"
+
+type Node interface {
+ ID() NodeID
+ Height() uint8
+ IsLeaf() bool
+ Size() int64
+ Version() uint32
+ Key() ([]byte, error)
+ Value() ([]byte, error)
+ Left() *NodePointer
+ Right() *NodePointer
+ Hash() []byte
+ SafeHash() []byte
+ MutateBranch(version uint32) (*MemNode, error)
+ Get(key []byte) (value []byte, index int64, err error)
+
+ fmt.Stringer
+}
diff --git a/iavlx/node_hash.go b/iavlx/node_hash.go
new file mode 100644
index 000000000000..89fadcc203c0
--- /dev/null
+++ b/iavlx/node_hash.go
@@ -0,0 +1,118 @@
+package iavlx
+
+import (
+ "crypto/sha256"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "io"
+ "sync"
+)
+
+func computeAndSetHash(node *MemNode, leftHash, rightHash []byte) ([]byte, error) {
+ h, err := computeHash(node, leftHash, rightHash)
+ if err != nil {
+ return nil, err
+ }
+ node.hash = h
+
+ return h, nil
+}
+
+var hasherPool = sync.Pool{
+ New: func() any {
+ return sha256.New()
+ },
+}
+
+func putBackHasher(h hash.Hash) {
+ h.Reset()
+ hasherPool.Put(h)
+}
+
+func computeHash(node Node, leftHash, rightHash []byte) ([]byte, error) {
+ hasher := hasherPool.Get().(hash.Hash)
+ defer putBackHasher(hasher)
+ if err := writeHashBytes(node, leftHash, rightHash, hasher); err != nil {
+ return nil, err
+ }
+ return hasher.Sum(nil), nil
+}
+
+var emptyHash = sha256.New().Sum(nil)
+
+func shaSum256(bz []byte) []byte {
+ hasher := hasherPool.Get().(hash.Hash)
+ defer putBackHasher(hasher)
+ hasher.Write(bz)
+ var sum [sha256.Size]byte
+ hasher.Sum(sum[:0])
+ return sum[:]
+}
+
+// Writes the node's hash to the given `io.Writer`. This function recursively calls
+// children to update hashes.
+func writeHashBytes(node Node, leftHash, rightHash []byte, w io.Writer) error {
+ var (
+ n int
+ buf [binary.MaxVarintLen64]byte
+ )
+
+ n = binary.PutVarint(buf[:], int64(node.Height()))
+ if _, err := w.Write(buf[0:n]); err != nil {
+ return fmt.Errorf("writing height, %w", err)
+ }
+ n = binary.PutVarint(buf[:], node.Size())
+ if _, err := w.Write(buf[0:n]); err != nil {
+ return fmt.Errorf("writing size, %w", err)
+ }
+ n = binary.PutVarint(buf[:], int64(node.Version()))
+ if _, err := w.Write(buf[0:n]); err != nil {
+ return fmt.Errorf("writing version, %w", err)
+ }
+
+ // Key is not written for inner nodes, unlike writeBytes.
+
+ if node.IsLeaf() {
+ key, err := node.Key()
+ if err != nil {
+ return fmt.Errorf("getting key, %w", err)
+ }
+
+ if err := encodeVarintPrefixedBytes(w, key); err != nil {
+ return fmt.Errorf("writing key, %w", err)
+ }
+
+ value, err := node.Value()
+ if err != nil {
+ return fmt.Errorf("getting value, %w", err)
+ }
+
+ // Indirection needed to provide proofs without values.
+ // (e.g. ProofLeafNode.ValueHash)
+ if err := encodeVarintPrefixedBytes(w, shaSum256(value)); err != nil {
+ return fmt.Errorf("writing value, %w", err)
+ }
+ } else {
+ if err := encodeVarintPrefixedBytes(w, leftHash); err != nil {
+ return fmt.Errorf("writing left hash, %w", err)
+ }
+ if err := encodeVarintPrefixedBytes(w, rightHash); err != nil {
+ return fmt.Errorf("writing right hash, %w", err)
+ }
+ }
+
+ return nil
+}
+
+// encodeVarintPrefixedBytes writes a varint length-prefixed byte slice to the writer,
+// it's used for hash computation, must be compactible with the official IAVL implementation.
+func encodeVarintPrefixedBytes(w io.Writer, bz []byte) error {
+ var buf [binary.MaxVarintLen64]byte
+ n := binary.PutUvarint(buf[:], uint64(len(bz)))
+ if _, err := w.Write(buf[0:n]); err != nil {
+ return err
+ }
+ _, err := w.Write(bz)
+ return err
+}
diff --git a/iavlx/node_id.go b/iavlx/node_id.go
new file mode 100644
index 000000000000..b21566a843cd
--- /dev/null
+++ b/iavlx/node_id.go
@@ -0,0 +1,44 @@
+package iavlx
+
+import "fmt"
+
+// NodeID is a stable identifier for a node in the IAVL tree.
+// Bit 63 indicates whether this is a leaf (1) or branch (0)
+// Bits 62-23 (40 bits) are for version.
+// Bits 22-0 (23 bits) are for index.
+// Valid index values start from 1. A zero index value may be used to indicate a null node.
+type NodeID uint64
+
+func NewNodeID(isLeaf bool, version uint64, index uint32) NodeID {
+ // check 40 bits for version and 23 bits for index
+ if version >= 0x10000000000 {
+ panic("version too large for NodeID")
+ }
+ if index >= 0x800000 {
+ panic("index too large for NodeID")
+ }
+ var id uint64
+ if isLeaf {
+ id |= 1 << 63
+ }
+ id |= (version & 0xFFFFFFFFFF) << 23
+ id |= uint64(index & 0x7FFFFF)
+ return NodeID(id)
+}
+
+func (id NodeID) IsLeaf() bool {
+ // check if highest bit is set
+ return id&(1<<63) != 0
+}
+
+func (id NodeID) Version() uint64 {
+ return (uint64(id) >> 23) & 0xFFFFFFFFFF
+}
+
+func (id NodeID) Index() uint32 {
+ return uint32(id & 0x7FFFFF)
+}
+
+func (id NodeID) String() string {
+ return fmt.Sprintf("NodeID{leaf:%t, version:%d, index:%d}", id.IsLeaf(), id.Version(), id.Index())
+}
diff --git a/iavlx/node_pointer.go b/iavlx/node_pointer.go
new file mode 100644
index 000000000000..26cc1c568fef
--- /dev/null
+++ b/iavlx/node_pointer.go
@@ -0,0 +1,31 @@
+package iavlx
+
+import (
+ "fmt"
+ "sync/atomic"
+)
+
+type NodePointer struct {
+ mem atomic.Pointer[MemNode]
+ store *Changeset
+ fileIdx uint32 // absolute index in file, 1-based, zero means we don't have an offset
+ id NodeID
+}
+
+func NewNodePointer(memNode *MemNode) *NodePointer {
+ n := &NodePointer{}
+ n.mem.Store(memNode)
+ return n
+}
+
+func (p *NodePointer) Resolve() (Node, error) {
+ mem := p.mem.Load()
+ if mem != nil {
+ return mem, nil
+ }
+ return p.store.Resolve(p.id, p.fileIdx)
+}
+
+func (p *NodePointer) String() string {
+ return fmt.Sprintf("NodePointer{id: %s, fileIdx: %d}", p.id.String(), p.fileIdx)
+}
diff --git a/iavlx/node_update.go b/iavlx/node_update.go
new file mode 100644
index 000000000000..dd2000860bd3
--- /dev/null
+++ b/iavlx/node_update.go
@@ -0,0 +1,357 @@
+package iavlx
+
+import "bytes"
+
+// setRecursive do set operation.
+// it always do modification and return new `MemNode`, even if the value is the same.
+// also returns if it's an update or insertion, if update, the tree height and balance is not changed.
+func setRecursive(nodePtr *NodePointer, leafNode *MemNode, ctx *MutationContext) (*NodePointer, bool, error) {
+ if nodePtr == nil {
+ return NewNodePointer(leafNode), true, nil
+ }
+
+ node, err := nodePtr.Resolve()
+ if err != nil {
+ return nil, false, err
+ }
+
+ nodeKey, err := node.Key()
+ if err != nil {
+ return nil, false, err
+ }
+ if node.IsLeaf() {
+ leafNodePtr := NewNodePointer(leafNode)
+ cmp := bytes.Compare(leafNode.key, nodeKey)
+ if cmp == 0 {
+ ctx.AddOrphan(nodePtr.id)
+ return leafNodePtr, true, nil
+ }
+ n := &MemNode{
+ height: 1,
+ size: 2,
+ version: ctx.Version,
+ }
+ switch cmp {
+ case -1:
+ n.left = leafNodePtr
+ n.right = nodePtr
+ n.key = nodeKey
+ // n._keyRef = node
+ case 1:
+ n.left = nodePtr
+ n.right = leafNodePtr
+ n.key = leafNode.key
+ // n._keyRef = leafNode
+ default:
+ panic("unreachable")
+ }
+ return NewNodePointer(n), false, nil
+ } else {
+ var (
+ newChildPtr *NodePointer
+ newNode *MemNode
+ updated bool
+ err error
+ )
+ if bytes.Compare(leafNode.key, nodeKey) == -1 {
+ newChildPtr, updated, err = setRecursive(node.Left(), leafNode, ctx)
+ if err != nil {
+ return nil, false, err
+ }
+ newNode, err = ctx.MutateBranch(node)
+ if err != nil {
+ return nil, false, err
+ }
+ newNode.left = newChildPtr
+ } else {
+ newChildPtr, updated, err = setRecursive(node.Right(), leafNode, ctx)
+ if err != nil {
+ return nil, false, err
+ }
+ newNode, err = ctx.MutateBranch(node)
+ if err != nil {
+ return nil, false, err
+ }
+ newNode.right = newChildPtr
+ }
+
+ if !updated {
+ err = newNode.updateHeightSize()
+ if err != nil {
+ return nil, false, err
+ }
+
+ newNode, err = newNode.reBalance(ctx)
+ if err != nil {
+ return nil, false, err
+ }
+ }
+
+ return NewNodePointer(newNode), updated, nil
+ }
+}
+
+type newKeyWrapper struct {
+ key []byte
+ // keyRef keyRefLink
+}
+
+// removeRecursive returns:
+// - (nil, origNode, nil) -> nothing changed in subtree
+// - (value, nil, nil) -> leaf node is removed
+// - (value, new node, newKey) -> subtree changed
+func removeRecursive(nodePtr *NodePointer, key []byte, ctx *MutationContext) (value []byte, newNodePtr *NodePointer, newKey *newKeyWrapper, err error) {
+ if nodePtr == nil {
+ return nil, nil, nil, nil
+ }
+
+ node, err := nodePtr.Resolve()
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ nodeKey, err := node.Key()
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ if node.IsLeaf() {
+ if bytes.Equal(nodeKey, key) {
+ ctx.AddOrphan(nodePtr.id)
+ value, err := node.Value()
+ return value, nil, nil, err
+ }
+ return nil, nodePtr, nil, nil
+ }
+
+ if bytes.Compare(key, nodeKey) == -1 {
+ value, newLeft, newKey, err := removeRecursive(node.Left(), key, ctx)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ if value == nil {
+ return nil, nodePtr, nil, nil
+ }
+
+ if newLeft == nil {
+ ctx.AddOrphan(nodePtr.id)
+ return value, node.Right(), &newKeyWrapper{
+ key: nodeKey,
+ // keyRef: nodePtr,
+ }, nil
+ }
+
+ newNode, err := ctx.MutateBranch(node)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ newNode.left = newLeft
+ err = newNode.updateHeightSize()
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ newNode, err = newNode.reBalance(ctx)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ return value, NewNodePointer(newNode), newKey, nil
+ }
+
+ value, newRight, newKey, err := removeRecursive(node.Right(), key, ctx)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ if value == nil {
+ return nil, nodePtr, nil, nil
+ }
+
+ if newRight == nil {
+ ctx.AddOrphan(nodePtr.id)
+ return value, node.Left(), nil, nil
+ }
+
+ newNode, err := ctx.MutateBranch(node)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ newNode.right = newRight
+ if newKey != nil {
+ newNode.key = newKey.key
+ // newNode._keyRef = newKey.keyRef
+ }
+
+ err = newNode.updateHeightSize()
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ newNode, err = newNode.reBalance(ctx)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ return value, NewNodePointer(newNode), nil, nil
+}
+
+// IMPORTANT: nodes called with this method must be new or copies first.
+// Code reviewers should use find usages to ensure that all callers follow this rule!
+func (node *MemNode) updateHeightSize() error {
+ leftNode, err := node.left.Resolve()
+ if err != nil {
+ return err
+ }
+
+ rightNode, err := node.right.Resolve()
+ if err != nil {
+ return err
+ }
+
+ node.height = maxUint8(leftNode.Height(), rightNode.Height()) + 1
+ node.size = leftNode.Size() + rightNode.Size()
+ return nil
+}
+
+func maxUint8(a, b uint8) uint8 {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+// IMPORTANT: nodes called with this method must be new or copies first.
+// Code reviewers should use find usages to ensure that all callers follow this rule!
+func (node *MemNode) reBalance(ctx *MutationContext) (*MemNode, error) {
+ balance, err := calcBalance(node)
+ if err != nil {
+ return nil, err
+ }
+ switch {
+ case balance > 1:
+ left, err := node.left.Resolve()
+ if err != nil {
+ return nil, err
+ }
+
+ leftBalance, err := calcBalance(left)
+ if err != nil {
+ return nil, err
+ }
+
+ if leftBalance >= 0 {
+ // left left
+ return node.rotateRight(ctx)
+ }
+
+ // left right
+ newLeft, err := ctx.MutateBranch(left)
+ if err != nil {
+ return nil, err
+ }
+ newLeft, err = newLeft.rotateLeft(ctx)
+ if err != nil {
+ return nil, err
+ }
+ node.left = NewNodePointer(newLeft)
+ return node.rotateRight(ctx)
+ case balance < -1:
+ right, err := node.right.Resolve()
+ if err != nil {
+ return nil, err
+ }
+
+ rightBalance, err := calcBalance(right)
+ if err != nil {
+ return nil, err
+ }
+
+ if rightBalance <= 0 {
+ // right right
+ return node.rotateLeft(ctx)
+ }
+
+ // right left
+ newRight, err := ctx.MutateBranch(right)
+ if err != nil {
+ return nil, err
+ }
+ newRight, err = newRight.rotateRight(ctx)
+ node.right = NewNodePointer(newRight)
+ return node.rotateLeft(ctx)
+ default:
+ // nothing changed
+ return node, err
+ }
+}
+
+func calcBalance(node Node) (int, error) {
+ leftNode, err := node.Left().Resolve()
+ if err != nil {
+ return 0, err
+ }
+
+ rightNode, err := node.Right().Resolve()
+ if err != nil {
+ return 0, err
+ }
+
+ return int(leftNode.Height()) - int(rightNode.Height()), nil
+}
+
+// IMPORTANT: nodes called with this method must be new or copies first.
+// Code reviewers should use find usages to ensure that all callers follow this rule!
+func (node *MemNode) rotateRight(ctx *MutationContext) (*MemNode, error) {
+ left, err := node.left.Resolve()
+ if err != nil {
+ return nil, err
+ }
+ newSelf, err := ctx.MutateBranch(left)
+ if err != nil {
+ return nil, err
+ }
+ node.left = left.Right()
+ newSelf.right = NewNodePointer(node)
+
+ err = node.updateHeightSize()
+ if err != nil {
+ return nil, err
+ }
+ err = newSelf.updateHeightSize()
+ if err != nil {
+ return nil, err
+ }
+
+ return newSelf, nil
+}
+
+// IMPORTANT: nodes called with this method must be new or copies first.
+// Code reviewers should use find usages to ensure that all callers follow this rule!
+func (node *MemNode) rotateLeft(ctx *MutationContext) (*MemNode, error) {
+ right, err := node.right.Resolve()
+ if err != nil {
+ return nil, err
+ }
+
+ newSelf, err := ctx.MutateBranch(right)
+ if err != nil {
+ return nil, err
+ }
+
+ node.right = right.Left()
+ newSelf.left = NewNodePointer(node)
+
+ err = node.updateHeightSize()
+ if err != nil {
+ return nil, err
+ }
+
+ err = newSelf.updateHeightSize()
+ if err != nil {
+ return nil, err
+ }
+
+ return newSelf, nil
+}
diff --git a/iavlx/non_blocking_queue.go b/iavlx/non_blocking_queue.go
new file mode 100644
index 000000000000..aaf89c20a82a
--- /dev/null
+++ b/iavlx/non_blocking_queue.go
@@ -0,0 +1,57 @@
+package iavlx
+
+import "sync"
+
+type NonBlockingQueue[T any] struct {
+ mu sync.Mutex
+ cond *sync.Cond
+ queue []T
+ closed bool
+}
+
+func NewNonBlockingQueue[T any]() *NonBlockingQueue[T] {
+ res := &NonBlockingQueue[T]{}
+ res.cond = sync.NewCond(&res.mu)
+ return res
+}
+
+func (q *NonBlockingQueue[T]) Send(item T) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ if q.closed {
+ panic("NonBlockingQueue is closed, can't send")
+ }
+ q.queue = append(q.queue, item)
+ q.cond.Signal()
+}
+
+func (q *NonBlockingQueue[T]) Receive() []T {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ for len(q.queue) == 0 && !q.closed {
+ q.cond.Wait()
+ }
+
+ res := q.queue
+ q.queue = nil
+ return res
+}
+
+func (q *NonBlockingQueue[T]) MaybeReceive() (batch []T, closed bool) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ if len(q.queue) == 0 && !q.closed {
+ return nil, false
+ }
+
+ res := q.queue
+ q.queue = nil
+ return res, q.closed
+}
+
+func (q *NonBlockingQueue[T]) Close() {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ q.closed = true
+ q.cond.Broadcast()
+}
diff --git a/iavlx/options.go b/iavlx/options.go
new file mode 100644
index 000000000000..ff1dfb174863
--- /dev/null
+++ b/iavlx/options.go
@@ -0,0 +1,100 @@
+package iavlx
+
+import "time"
+
+type Options struct {
+ // EvictDepth defines the depth at which eviction occurs. 255 means no eviction.
+ EvictDepth uint8 `json:"evict_depth"`
+
+ // WriteWAL enables write-ahead logging for durability
+ WriteWAL bool `json:"write_wal"`
+ // CompactWAL determines if KV data is copied during compaction (true) or reused (false)
+ CompactWAL bool `json:"compact_wal"`
+ // DisableCompaction turns off background compaction entirely
+ DisableCompaction bool `json:"disable_compaction"`
+
+ // CompactionOrphanRatio is the orphan/total ratio (0-1) that triggers compaction
+ CompactionOrphanRatio float64 `json:"compaction_orphan_ratio"`
+ // CompactionOrphanAge is the average age of orphans (in versions) at which compaction is triggered
+ CompactionOrphanAge uint32 `json:"compaction_orphan_age"`
+
+ // RetainVersions is the number of recent versions to keep uncompacted.
+ // If this is set to 0, all versions will be retained, and the compactor will only join changesets without removing any.
+ RetainVersions uint32 `json:"retain_versions"`
+ // MinCompactionSeconds is the minimum interval between compaction runs
+ MinCompactionSeconds uint32 `json:"min_compaction_seconds"`
+ // ChangesetMaxTarget is the maximum size of a changeset file when batching new versions
+ ChangesetMaxTarget uint32 `json:"changeset_max_target"`
+ // CompactionMaxTarget is the maximum size when joining/compacting old changesets
+ CompactionMaxTarget uint32 `json:"compaction_max_target"`
+ // CompactAfterVersions is the number of versions after which a full compaction is forced whenever there are orphans
+ CompactAfterVersions uint32 `json:"compact_after_versions"`
+ // ReaderUpdateInterval controls how often we create new mmap readers during batching (in versions)
+ // Setting to 0 means create reader every version (high mmap churn)
+ // Higher values reduce mmap overhead but delay when data becomes readable
+ ReaderUpdateInterval uint32 `json:"reader_update_interval"`
+
+ // FsyncInterval defines how often to fsync WAL when using async mode (in millisconds).
+ FsyncInterval int `json:"fsync_interval"`
+
+ // ZeroCopy attempts to reduce copying of buffers, but this isn't really implemented yet and may not even be safe to implement.
+ ZeroCopy bool `json:"zero_copy"`
+}
+
+// GetCompactionOrphanAge returns the orphan age threshold with default
+func (o Options) GetCompactionOrphanAge() uint32 {
+ if o.CompactionOrphanAge == 0 {
+ return 10 // Default to 10 versions
+ }
+ return o.CompactionOrphanAge
+}
+
+// GetCompactionOrphanRatio returns the orphan ratio threshold with default
+func (o Options) GetCompactionOrphanRatio() float64 {
+ if o.CompactionOrphanRatio <= 0 {
+ return 0.6 // Default to 60% orphans
+ }
+ return o.CompactionOrphanRatio
+}
+
+// GetChangesetMaxTarget returns the max changeset size with default
+func (o Options) GetChangesetMaxTarget() uint64 {
+ if o.ChangesetMaxTarget == 0 {
+ return 128 * 1024 * 1024 // 128MB default for changesets
+ }
+ return uint64(o.ChangesetMaxTarget)
+}
+
+// GetCompactionMaxTarget returns the max size for compaction with default
+func (o Options) GetCompactionMaxTarget() uint64 {
+ if o.CompactionMaxTarget == 0 {
+ return 1024 * 1024 * 1024 // 1GB default for compaction
+ }
+ return uint64(o.CompactionMaxTarget)
+}
+
+func (o Options) GetCompactAfterVersions() uint32 {
+ if o.CompactAfterVersions == 0 {
+ return 500 // default to 500 versions
+ }
+ return o.CompactAfterVersions
+}
+
+// GetReaderUpdateInterval returns the interval for creating readers with default
+func (o Options) GetReaderUpdateInterval() uint32 {
+ if o.ReaderUpdateInterval == 0 {
+ return 100 // Default to updating reader every 100 versions
+ }
+ return o.ReaderUpdateInterval
+}
+
+func (o Options) FsyncEnabled() bool {
+ return o.FsyncInterval != 0
+}
+
+func (o Options) GetFsyncInterval() time.Duration {
+ if o.FsyncInterval < 0 {
+ return 0
+ }
+ return time.Millisecond * time.Duration(o.FsyncInterval)
+}
diff --git a/iavlx/orphans.go b/iavlx/orphans.go
new file mode 100644
index 000000000000..e2cb2395753f
--- /dev/null
+++ b/iavlx/orphans.go
@@ -0,0 +1,61 @@
+package iavlx
+
+import (
+ "bufio"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "os"
+)
+
+type OrphanWriter struct {
+ *FileWriter
+}
+
+func NewOrphanWriter(file *os.File) *OrphanWriter {
+ return &OrphanWriter{
+ FileWriter: NewFileWriter(file),
+ }
+}
+
+func (ow *OrphanWriter) WriteOrphan(version uint32, id NodeID) error {
+ var bz [12]byte
+ binary.LittleEndian.PutUint32(bz[0:4], version)
+ binary.LittleEndian.PutUint64(bz[4:12], uint64(id))
+ _, err := ow.Write(bz[:])
+ return err
+}
+
+func (ow *OrphanWriter) WriteOrphanMap(orphanMap map[NodeID]uint32) error {
+ for id, version := range orphanMap {
+ if err := ow.WriteOrphan(version, id); err != nil {
+ return err
+ }
+ }
+
+ return ow.Flush()
+}
+
+func ReadOrphanMap(file *os.File) (map[NodeID]uint32, error) {
+ file2, err := os.Open(file.Name())
+ if err != nil {
+ return nil, fmt.Errorf("failed to open orphan file for reading: %w", err)
+ }
+ orphanMap := make(map[NodeID]uint32)
+ rdr := bufio.NewReader(file2)
+ var buf [12]byte
+ for {
+ _, err := rdr.Read(buf[:])
+ if err != nil {
+ if err == io.EOF {
+ return orphanMap, nil
+ }
+ return nil, err
+ }
+ version := binary.LittleEndian.Uint32(buf[0:4])
+ id := NodeID(binary.LittleEndian.Uint64(buf[4:12]))
+ if _, exists := orphanMap[id]; !exists {
+ orphanMap[id] = version
+ }
+ }
+}
diff --git a/iavlx/reader.go b/iavlx/reader.go
new file mode 100644
index 000000000000..b9c9c82ac469
--- /dev/null
+++ b/iavlx/reader.go
@@ -0,0 +1,131 @@
+package iavlx
+
+import (
+ "fmt"
+ "os"
+ "unsafe"
+)
+
+// check little endian at init time
+func init() {
+ buf := [2]byte{}
+ *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
+
+ if buf != [2]byte{0xCD, 0xAB} {
+ panic("native byte order is not little endian, please build without nativebyteorder")
+ }
+}
+
+type StructMmap[T any] struct {
+ items []T
+ file *MmapFile
+ size int
+}
+
+func NewStructReader[T any](file *os.File) (*StructMmap[T], error) {
+ mmap, err := NewMmapFile(file)
+ if err != nil {
+ return nil, err
+ }
+
+ var zero T
+ df := &StructMmap[T]{
+ file: mmap,
+ size: int(unsafe.Sizeof(zero)),
+ }
+
+ buf := mmap.Data()
+ p := unsafe.Pointer(unsafe.SliceData(buf))
+ align := unsafe.Alignof(zero)
+ if uintptr(p)%align != 0 {
+ return nil, fmt.Errorf("input buffer is not aligned: %p", p)
+ }
+
+ size := df.size
+ if len(buf)%size != 0 {
+ return nil, fmt.Errorf("input buffer size is not a multiple of struct size: %d %% %d != 0", len(buf), size)
+ }
+ data := unsafe.Slice((*T)(p), len(buf)/size)
+ df.items = data
+
+ return df, nil
+}
+
+func (df *StructMmap[T]) UnsafeItem(i uint32) *T {
+ return &df.items[i]
+}
+
+func (df *StructMmap[T]) Count() int {
+ return len(df.items)
+}
+
+func (df *StructMmap[T]) TotalBytes() int {
+ return df.file.TotalBytes()
+}
+
+func (df *StructMmap[T]) Close() error {
+ return df.file.Close()
+}
+
+type NodeLayout interface {
+ ID() NodeID
+}
+
+type NodeMmap[T NodeLayout] struct {
+ *StructMmap[T]
+}
+
+func NewNodeReader[T NodeLayout](file *os.File) (*NodeMmap[T], error) {
+ sf, err := NewStructReader[T](file)
+ if err != nil {
+ return nil, err
+ }
+ return &NodeMmap[T]{StructMmap: sf}, nil
+}
+
+func (nf *NodeMmap[T]) FindByID(id NodeID, info *NodeSetInfo) (*T, error) {
+ // binary search with interpolation
+ lowOffset := info.StartOffset
+ targetIdx := id.Index()
+ lowIdx := info.StartIndex
+ highOffset := lowOffset + info.Count - 1
+ highIdx := info.EndIndex
+ for lowOffset <= highOffset {
+ if targetIdx < lowIdx || targetIdx > highIdx {
+ return nil, fmt.Errorf("node ID %s not present", id.String())
+ }
+ // If nodes are contiguous in this range, compute offset directly
+ if highIdx-lowIdx == highOffset-lowOffset {
+ targetOffset := lowOffset + (targetIdx - lowIdx)
+ return &nf.items[targetOffset], nil
+ }
+ // Interpolation search: estimate position based on target's relative position in index range
+ var mid uint32
+ if highIdx > lowIdx {
+ // Estimate where target should be based on its position in the index range
+ fraction := float64(targetIdx-lowIdx) / float64(highIdx-lowIdx)
+ mid = lowOffset + uint32(fraction*float64(highOffset-lowOffset))
+ // Ensure mid stays within bounds
+ if mid < lowOffset {
+ mid = lowOffset
+ } else if mid > highOffset {
+ mid = highOffset
+ }
+ } else {
+ // When indices converge, use simple midpoint
+ mid = (lowOffset + highOffset) / 2
+ }
+ midNode := &nf.items[mid]
+ midIdx := (*midNode).ID().Index()
+ if midIdx == targetIdx {
+ return midNode, nil
+ } else if midIdx < targetIdx {
+ lowOffset = mid + 1
+ lowIdx = midIdx + 1
+ } else {
+ highOffset = mid - 1
+ highIdx = midIdx - 1
+ }
+ }
+ return nil, fmt.Errorf("node ID %s not found", id.String())
+}
diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go
new file mode 100644
index 000000000000..c4bf4cf0f946
--- /dev/null
+++ b/iavlx/tree_store.go
@@ -0,0 +1,384 @@
+package iavlx
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/tidwall/btree"
+
+ "cosmossdk.io/log"
+)
+
+type TreeStore struct {
+ logger log.Logger
+ dir string
+
+ currentWriter *ChangesetWriter
+ currentChangesetEntry *changesetEntry // Entry for the current batch being written
+ changesets *btree.Map[uint32, *changesetEntry]
+ changesetsMapLock sync.RWMutex
+ savedVersion atomic.Uint32 // Last version with a readable changeset
+ stagedVersion uint32 // Latest written version (may not be readable yet)
+
+ opts Options
+
+ syncQueue *NonBlockingQueue[*ChangesetWriter]
+ syncDone chan error
+
+ cleanupProc *cleanupProc
+}
+
+type markOrphansReq struct {
+ version uint32
+ orphans [][]NodeID
+}
+
+type changesetEntry struct {
+ changeset atomic.Pointer[Changeset]
+}
+
+func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, error) {
+ ts := &TreeStore{
+ dir: dir,
+ changesets: &btree.Map[uint32, *changesetEntry]{},
+ logger: logger,
+ opts: options,
+ stagedVersion: 1,
+ }
+
+ err := ts.load()
+ if err != nil {
+ return nil, fmt.Errorf("failed to load existing changesets: %w", err)
+ }
+
+ err = ts.initNewWriter()
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize first writer: %w", err)
+ }
+
+ ts.cleanupProc = newCleanupProc(ts)
+
+ if options.FsyncEnabled() {
+ ts.syncQueue = NewNonBlockingQueue[*ChangesetWriter]()
+ ts.syncDone = make(chan error)
+ go ts.syncProc()
+ }
+
+ return ts, nil
+}
+
+func (ts *TreeStore) initNewWriter() error {
+ stagedVersion := ts.savedVersion.Load() + 1
+ writer, err := NewChangesetWriter(ts.dir, stagedVersion, ts)
+ if err != nil {
+ return fmt.Errorf("failed to create changeset writer: %w", err)
+ }
+ ts.currentWriter = writer
+
+ return nil
+}
+
+func (ts *TreeStore) getChangesetEntryForVersion(version uint32) *changesetEntry {
+ ts.changesetsMapLock.RLock()
+ defer ts.changesetsMapLock.RUnlock()
+
+ var res *changesetEntry
+ // Find the changeset with the highest start version <= the requested version
+ ts.changesets.Descend(version, func(key uint32, cs *changesetEntry) bool {
+ res = cs
+ return false // Take the first (highest) entry <= version
+ })
+ return res
+}
+
+func (ts *TreeStore) getChangesetForVersion(version uint32) *Changeset {
+ cs := ts.getChangesetEntryForVersion(version)
+ if cs == nil {
+ return nil
+ } else {
+ return cs.changeset.Load()
+ }
+}
+
+func (ts *TreeStore) ReadK(nodeId NodeID) (key []byte, err error) {
+ cs := ts.getChangesetForVersion(uint32(nodeId.Version()))
+ cs.Pin()
+ defer cs.Unpin()
+
+ if cs == nil {
+ return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version())
+ }
+
+ var offset uint32
+ if nodeId.IsLeaf() {
+ leaf, err := cs.ResolveLeaf(nodeId, 0)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err)
+ }
+ offset = leaf.KeyOffset
+ } else {
+ branch, err := cs.ResolveBranch(nodeId, 0)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve branch %s: %w", nodeId.String(), err)
+ }
+ offset = branch.KeyOffset
+ }
+
+ return cs.ReadK(nodeId, offset)
+}
+
+func (ts *TreeStore) ReadKV(nodeId NodeID) (key, value []byte, err error) {
+ if !nodeId.IsLeaf() {
+ return nil, nil, fmt.Errorf("node %s is not a leaf", nodeId.String())
+ }
+
+ cs := ts.getChangesetForVersion(uint32(nodeId.Version()))
+ if cs == nil {
+ return nil, nil, fmt.Errorf("no changeset found for version %d", nodeId.Version())
+ }
+
+ cs.Pin()
+ defer cs.Unpin()
+
+ leaf, err := cs.ResolveLeaf(nodeId, 0)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err)
+ }
+
+ return cs.ReadKV(nodeId, leaf.KeyOffset)
+}
+
+func (ts *TreeStore) ReadV(nodeId NodeID) ([]byte, error) {
+ // TODO reduce code duplication with ReadKV
+
+ if !nodeId.IsLeaf() {
+ return nil, fmt.Errorf("node %s is not a leaf", nodeId.String())
+ }
+
+ cs := ts.getChangesetForVersion(uint32(nodeId.Version()))
+ if cs == nil {
+ return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version())
+ }
+
+ cs.Pin()
+ defer cs.Unpin()
+ leaf, err := cs.ResolveLeaf(nodeId, 0)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err)
+ }
+
+ return cs.ReadV(nodeId, leaf.KeyOffset)
+}
+
+func (ts *TreeStore) ResolveLeaf(nodeId NodeID) (LeafLayout, error) {
+ cs := ts.getChangesetForVersion(uint32(nodeId.Version()))
+ if cs == nil {
+ return LeafLayout{}, fmt.Errorf("no changeset found for version %d", nodeId.Version())
+ }
+ return cs.ResolveLeaf(nodeId, 0)
+}
+
+func (ts *TreeStore) ResolveBranch(nodeId NodeID) (BranchLayout, error) {
+ cs := ts.getChangesetForVersion(uint32(nodeId.Version()))
+ if cs == nil {
+ return BranchLayout{}, fmt.Errorf("no changeset found for version %d", nodeId.Version())
+ }
+ return cs.ResolveBranch(nodeId, 0)
+}
+
+func (ts *TreeStore) Resolve(nodeId NodeID, _ uint32) (Node, error) {
+ cs := ts.getChangesetForVersion(uint32(nodeId.Version()))
+ if cs == nil {
+ return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version())
+ }
+
+ return cs.Resolve(nodeId, 0)
+}
+
+func (ts *TreeStore) ResolveRoot(version uint32) (*NodePointer, error) {
+ cs := ts.getChangesetForVersion(version)
+ if cs == nil {
+ return nil, fmt.Errorf("no changeset found for version %d", version)
+ }
+ return cs.ResolveRoot(version)
+}
+
+func (ts *TreeStore) SavedVersion() uint32 {
+ return ts.savedVersion.Load()
+}
+
+func (ts *TreeStore) WriteWALUpdates(updates []KVUpdate) error {
+ return ts.currentWriter.WriteWALUpdates(updates)
+}
+
+func (ts *TreeStore) WriteWALCommit(version uint32) error {
+ return ts.currentWriter.WriteWALCommit(version)
+}
+
+func (ts *TreeStore) SaveRoot(root *NodePointer, totalLeaves, totalBranches uint32) error {
+ version := ts.stagedVersion
+ ts.logger.Debug("saving root", "version", version)
+ err := ts.currentWriter.SaveRoot(root, version, totalLeaves, totalBranches)
+ if err != nil {
+ return err
+ }
+ ts.stagedVersion++
+
+ currentSize := ts.currentWriter.TotalBytes()
+ maxSize := ts.opts.GetChangesetMaxTarget()
+ readerInterval := ts.opts.GetReaderUpdateInterval()
+
+ ts.logger.Debug("saved root", "version", version, "changeset_size", currentSize, "max_size", maxSize, "start_version", ts.currentWriter.StartVersion())
+
+ // Queue changeset for async WAL sync if enabled
+ if ts.syncQueue != nil {
+ select {
+ case err := <-ts.syncDone:
+ if err != nil {
+ return err
+ }
+ default:
+ }
+ }
+
+ // Determine if we should create a reader
+ shouldCreateReader := false
+ shouldSeal := uint64(currentSize) >= maxSize
+
+ startVersion := ts.currentWriter.StartVersion()
+ if shouldSeal {
+ shouldCreateReader = true
+ } else if readerInterval > 0 {
+ // Create reader periodically based on interval
+ versions := version - startVersion + 1
+ if versions%readerInterval == 0 {
+ shouldCreateReader = true
+ }
+ }
+
+ if !shouldCreateReader {
+ // Just continue batching without creating reader
+ return nil
+ }
+
+ // Create reader (either shared or sealed)
+ var reader *Changeset
+ if shouldSeal {
+ // Size limit reached - seal the current batch
+ curWriter := ts.currentWriter
+ reader, err = curWriter.Seal()
+ if err != nil {
+ return fmt.Errorf("failed to seal changeset for version %d: %w", version, err)
+ }
+ if ts.syncQueue != nil {
+ // if sync queue is enabled, queue the files for fsync
+ ts.syncQueue.Send(curWriter)
+ }
+ } else {
+ // Create shared reader for periodic update
+ reader, err = ts.currentWriter.CreatedSharedReader()
+ if err != nil {
+ return fmt.Errorf("failed to create updated changeset reader: %w", err)
+ }
+ }
+
+ ts.setActiveReader(startVersion, reader)
+ ts.savedVersion.Store(version)
+
+ if shouldSeal {
+ ts.currentChangesetEntry = nil // Reset for next batch
+
+ // Create new writer for next batch
+ err = ts.initNewWriter()
+ if err != nil {
+ return fmt.Errorf("failed to initialize new writer after sealing version %d: %w", version, err)
+ }
+ }
+
+ return nil
+}
+
+func (ts *TreeStore) setActiveReader(version uint32, reader *Changeset) {
+ if ts.currentChangesetEntry == nil {
+ // First time we're creating an entry for this batch
+ ts.currentChangesetEntry = &changesetEntry{}
+ ts.currentChangesetEntry.changeset.Store(reader)
+
+ // Register at the start version only
+ ts.changesetsMapLock.Lock()
+ ts.changesets.Set(version, ts.currentChangesetEntry)
+ ts.changesetsMapLock.Unlock()
+ } else {
+ // Update existing entry with new reader
+ oldReader := ts.currentChangesetEntry.changeset.Swap(reader)
+ if oldReader != nil {
+ oldReader.Evict()
+ if !oldReader.TryDispose() {
+ ts.cleanupProc.addPendingDisposal(oldReader)
+ }
+ }
+ }
+}
+
+func (ts *TreeStore) MarkOrphans(version uint32, nodeIds [][]NodeID) {
+ ts.cleanupProc.markOrphans(version, nodeIds)
+}
+
+func (ts *TreeStore) syncProc() {
+ tick := time.NewTicker(ts.opts.GetFsyncInterval())
+ defer close(ts.syncDone)
+ for {
+ <-tick.C
+ curWriter := ts.currentWriter
+ err := curWriter.SyncWAL()
+ if err != nil {
+ ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err)
+ return
+ }
+ needsSync, closed := ts.syncQueue.MaybeReceive()
+ for _, f := range needsSync {
+ if err := f.SyncWAL(); err != nil {
+ ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err)
+ return
+ }
+ }
+ if closed {
+ return
+ }
+ }
+}
+
+func (ts *TreeStore) Close() error {
+ // save the current writer if it has uncommitted data
+ startVersion := ts.currentWriter.files.info.StartVersion
+ if startVersion != 0 {
+ cs, err := ts.currentWriter.Seal()
+ if err != nil {
+ return fmt.Errorf("failed to seal current changeset on close: %w", err)
+ }
+ ts.setActiveReader(startVersion, cs)
+ ts.savedVersion.Store(ts.currentWriter.files.info.EndVersion)
+ }
+
+ ts.cleanupProc.shutdown()
+
+ if ts.syncQueue != nil {
+ ts.syncQueue.Close()
+ err := <-ts.syncDone
+ if err != nil {
+ return err
+ }
+ }
+
+ ts.changesetsMapLock.Lock()
+
+ var errs []error
+ ts.changesets.Scan(func(version uint32, entry *changesetEntry) bool {
+ errs = append(errs, entry.changeset.Load().Close())
+ return true
+ })
+ return errors.Join(errs...)
+}
diff --git a/iavlx/tree_test.go b/iavlx/tree_test.go
new file mode 100644
index 000000000000..4de85fb91d83
--- /dev/null
+++ b/iavlx/tree_test.go
@@ -0,0 +1,422 @@
+package iavlx
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "runtime/debug"
+ "testing"
+
+ "github.com/cosmos/iavl"
+ dbm "github.com/cosmos/iavl/db"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/exp/maps"
+ "pgregory.net/rapid"
+
+ corestore "cosmossdk.io/core/store"
+ sdklog "cosmossdk.io/log"
+
+ storetypes "cosmossdk.io/store/types"
+)
+
+// TODO: this test isn't for expected behavior.
+// It should eventually be updated such that having a default ReaderUpdateInterval shouldn't error on old version queries.
+func TestTree_ErrorsOnOldVersion(t *testing.T) {
+ testCases := []struct {
+ name string
+ getTree func() *CommitTree
+ expError error
+ }{
+ {
+ name: "should error",
+ getTree: func() *CommitTree {
+ dir := t.TempDir()
+ commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger())
+ require.NoError(t, err)
+ return commitTree
+ },
+ // TODO: this shouldn't error!
+ expError: fmt.Errorf("no changeset found for version 2"),
+ },
+ {
+ name: "should NOT error",
+ getTree: func() *CommitTree {
+ dir := t.TempDir()
+ commitTree, err := NewCommitTree(dir, Options{ReaderUpdateInterval: 1}, sdklog.NewNopLogger())
+ require.NoError(t, err)
+ return commitTree
+ },
+ expError: nil,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ commitTree := tc.getTree()
+ for range 7 {
+ tree := commitTree.CacheWrap().(storetypes.CacheKVStore)
+ tree.Set([]byte{0}, []byte{1})
+ tree.Write()
+ commitTree.Commit()
+ }
+ _, err := commitTree.GetImmutable(2)
+ require.Equal(t, tc.expError, err)
+ })
+ }
+
+}
+
+func TestTree_NonExistentChangeset(t *testing.T) {
+ dir := t.TempDir()
+ commitTree, err := NewCommitTree(dir, Options{ReaderUpdateInterval: 1}, sdklog.NewNopLogger())
+ require.NoError(t, err)
+
+ for range 7 {
+ tree := commitTree.CacheWrap().(storetypes.CacheKVStore)
+ tree.Set([]byte{0}, []byte{1})
+ tree.Write()
+ commitTree.Commit()
+ }
+
+ _, err = commitTree.GetImmutable(2)
+ require.NoError(t, err)
+}
+
+func TestBasicTest(t *testing.T) {
+ dir, err := os.MkdirTemp("", "iavlx")
+ require.NoError(t, err)
+ defer os.RemoveAll(dir)
+ commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger())
+ require.NoError(t, err)
+ tree := commitTree.CacheWrap().(storetypes.CacheKVStore)
+ tree.Set([]byte{0}, []byte{1})
+ // renderTree(t, tree)
+
+ val := tree.Get([]byte{0})
+ require.NoError(t, err)
+ require.Equal(t, []byte{1}, val)
+
+ tree.Set([]byte{1}, []byte{2})
+ // renderTree(t, tree)
+
+ val = tree.Get([]byte{0})
+ require.NoError(t, err)
+ require.Equal(t, []byte{1}, val)
+ val = tree.Get([]byte{1})
+ require.NoError(t, err)
+ require.Equal(t, []byte{2}, val)
+
+ tree.Set([]byte{2}, []byte{3})
+ // renderTree(t, tree)
+
+ val = tree.Get([]byte{0})
+ require.NoError(t, err)
+ require.Equal(t, []byte{1}, val)
+ val = tree.Get([]byte{1})
+ require.NoError(t, err)
+ require.Equal(t, []byte{2}, val)
+ val = tree.Get([]byte{2})
+ require.NoError(t, err)
+ require.Equal(t, []byte{3}, val)
+
+ val = tree.Get([]byte{3})
+ require.NoError(t, err)
+ require.Nil(t, val)
+
+ tree.Delete([]byte{1})
+ // renderTree(t, tree)
+
+ val = tree.Get([]byte{1})
+ require.NoError(t, err)
+ require.Nil(t, val)
+
+ tree.Write()
+ commitId := commitTree.Commit()
+ require.NoError(t, err)
+ require.NotNil(t, commitId)
+ t.Logf("committed with root commitId: %X", commitId)
+ require.NoError(t, commitTree.Close())
+}
+
+func renderTree(t interface {
+ require.TestingT
+ Logf(format string, args ...any)
+}, tree *ImmutableTree,
+) {
+ graph := &bytes.Buffer{}
+ require.NoError(t, RenderNodeDotGraph(graph, tree.root))
+ t.Logf("tree graph:\n%s", graph.String())
+}
+
+func TestIAVLXSims(t *testing.T) {
+ rapid.Check(t, testIAVLXSims)
+}
+
+func FuzzIAVLX(f *testing.F) {
+ f.Fuzz(rapid.MakeFuzz(testIAVLXSims))
+}
+
+func testIAVLXSims(t *rapid.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ t.Fatalf("panic recovered: %v\nStack trace:\n%s", r, debug.Stack())
+ }
+ }()
+ // logger := sdklog.NewTestLogger(t)
+ logger := sdklog.NewNopLogger()
+ dbV1 := dbm.NewMemDB()
+ treeV1 := iavl.NewMutableTree(dbV1, 500000, true, logger)
+
+ tempDir, err := os.MkdirTemp("", "iavlx")
+ require.NoError(t, err, "failed to create temp directory")
+ defer os.RemoveAll(tempDir)
+ simMachine := &SimMachine{
+ treeV1: treeV1,
+ dirV2: tempDir,
+ existingKeys: map[string][]byte{},
+ }
+ simMachine.openV2Tree(t)
+
+ // TODO switch from StateMachineActions to manually setting up the actions map, this is going to be too magical for other maintainers otherwise
+ t.Repeat(map[string]func(*rapid.T){
+ "": simMachine.Check,
+ "UpdateN": simMachine.UpdateN,
+ "GetN": simMachine.GetN,
+ "Iterate": simMachine.Iterate,
+ "Commit": simMachine.Commit,
+ })
+
+ require.NoError(t, treeV1.Close(), "failed to close iavl tree")
+ require.NoError(t, simMachine.treeV2.Close(), "failed to close iavlx tree")
+}
+
+type SimMachine struct {
+ treeV1 *iavl.MutableTree
+ treeV2 *CommitTree
+ dirV2 string
+ // existingKeys keeps track of keys that have been set in the tree or deleted. Deleted keys are retained as nil values.
+ existingKeys map[string][]byte
+}
+
+func (s *SimMachine) openV2Tree(t interface {
+ require.TestingT
+ sdklog.TestingT
+}) {
+ var err error
+ s.treeV2, err = NewCommitTree(s.dirV2, Options{
+ WriteWAL: true,
+ CompactWAL: true,
+ DisableCompaction: true,
+ ZeroCopy: false,
+ EvictDepth: 0,
+ CompactionOrphanRatio: 0,
+ CompactionOrphanAge: 0,
+ RetainVersions: 0,
+ MinCompactionSeconds: 0,
+ ChangesetMaxTarget: 1,
+ CompactAfterVersions: 0,
+ ReaderUpdateInterval: 1,
+ }, sdklog.NewTestLogger(t))
+ require.NoError(t, err, "failed to create iavlx tree")
+}
+
+func (s *SimMachine) Check(t *rapid.T) {
+ // after every operation verify the iavlx tree
+ // after every operation we check that both trees are identical
+ s.compareIterators(t, nil, nil, true)
+}
+
+func (s *SimMachine) UpdateN(t *rapid.T) {
+ n := rapid.IntRange(1, 5000).Draw(t, "n")
+ for i := 0; i < n; i++ {
+ del := rapid.Bool().Draw(t, "del")
+ if del {
+ s.delete(t)
+ } else {
+ s.set(t)
+ }
+ }
+}
+
+func (s *SimMachine) GetN(t *rapid.T) {
+ n := rapid.IntRange(1, 5000).Draw(t, "n")
+ for i := 0; i < n; i++ {
+ s.get(t)
+ }
+}
+
+func (s *SimMachine) set(t *rapid.T) {
+ // choose either a new or an existing key
+ key := s.selectKey(t)
+ value := rapid.SliceOfN(rapid.Byte(), 0, 10).Draw(t, "value")
+ // set in both trees
+ updated, errV1 := s.treeV1.Set(key, value)
+ require.NoError(t, errV1, "failed to set key in V1 tree")
+ branch := s.treeV2.CacheWrap().(storetypes.CacheKVStore)
+ branch.Set(key, value)
+ branch.Write()
+ // require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees")
+ if updated {
+ require.NotNil(t, s.existingKeys[string(key)], "key shouldn't have been marked as updated")
+ } else {
+ existing, found := s.existingKeys[string(key)]
+ if found {
+ require.Nil(t, existing, value, "marked as not an update but existin key is nil")
+ }
+ }
+ s.existingKeys[string(key)] = value // mark as existing
+}
+
+func (s *SimMachine) get(t *rapid.T) {
+ key := s.selectKey(t)
+ valueV1, errV1 := s.treeV1.Get(key)
+ require.NoError(t, errV1, "failed to get key from V1 tree")
+ valueV2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Get(key)
+ require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees")
+ expectedValue, found := s.existingKeys[string(key)]
+ if found {
+ require.Equal(t, expectedValue, valueV1, "expected value mismatch for key %s", key)
+ } else {
+ require.Nil(t, valueV1, "expected nil value for non-existing key %s", key)
+ }
+}
+
+func (s *SimMachine) selectKey(t *rapid.T) []byte {
+ if len(s.existingKeys) > 0 && rapid.Bool().Draw(t, "existingKey") {
+ return []byte(rapid.SampledFrom(maps.Keys(s.existingKeys)).Draw(t, "key"))
+ } else {
+ // TODO consider testing longer keys
+ return rapid.SliceOfN(rapid.Byte(), 1, 10).Draw(t, "key")
+ }
+}
+
+func (s *SimMachine) delete(t *rapid.T) {
+ key := s.selectKey(t)
+ existingValue, found := s.existingKeys[string(key)]
+ exists := found && existingValue != nil
+ // delete in both trees
+ _, removedV1, errV1 := s.treeV1.Remove(key)
+ require.NoError(t, errV1, "failed to remove key from V1 tree")
+ branch := s.treeV2.CacheWrap().(storetypes.CacheKVStore)
+ branch.Delete(key)
+ branch.Write()
+ // require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees")
+ // TODO v1 & v2 have slightly different behaviors for the value returned on removal. We should re-enable this and check.
+ //if valueV1 == nil || len(valueV1) == 0 {
+ // require.Empty(t, valueV2, "value should be empty for removed key in V2 tree")
+ //} else {
+ // require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees")
+ //}
+ require.Equal(t, exists, removedV1, "removed status should match existence of key")
+ s.existingKeys[string(key)] = nil // mark as deleted
+}
+
+func (s *SimMachine) Iterate(t *rapid.T) {
+ start := s.selectKey(t)
+ end := s.selectKey(t)
+ // make sure end is after start
+ if string(end) <= string(start) {
+ temp := start
+ start = end
+ end = temp
+ }
+
+ // TODO add cases where we nudge start or end up or down a little
+
+ // ascending := rapid.Bool().Draw(t, "ascending")
+
+ // s.compareIterators(t, start, end, ascending)
+}
+
+func (s *SimMachine) Commit(t *rapid.T) {
+ hash1, _, err := s.treeV1.SaveVersion()
+ require.NoError(t, err, "failed to save version in V1 tree")
+ commitId2 := s.treeV2.Commit()
+ require.NoError(t, err, "failed to save version in V2 tree")
+ err = VerifyTree(s.treeV2)
+ require.NoError(t, err, "failed to verify V2 tree")
+ require.Equal(t, hash1, commitId2.Hash, "hash mismatch between V1 and V2 trees")
+ closeReopen := rapid.Bool().Draw(t, "closeReopen")
+ if closeReopen {
+ require.NoError(t, s.treeV2.Close())
+ s.openV2Tree(t)
+ }
+}
+
+func (s *SimMachine) debugDump(t *rapid.T) {
+ version := s.treeV1.Version()
+ t.Logf("Dumping trees at version %d", version)
+ graph1 := &bytes.Buffer{}
+ iavl.WriteDOTGraph(graph1, s.treeV1.ImmutableTree, nil)
+ t.Logf("V1 tree:\n%s", graph1.String())
+ // renderTree(t, s.treeV2.Branch())
+ iter2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Iterator(nil, nil)
+ s.debugDumpTree(t, iter2)
+}
+
+func (s *SimMachine) debugDumpTree(t *rapid.T, iter corestore.Iterator) {
+ dumpStr := "Tree dump:"
+ defer func() {
+ require.NoError(t, iter.Close(), "failed to close iterator")
+ }()
+ for iter.Valid() {
+ key := iter.Key()
+ value := iter.Value()
+ dumpStr += fmt.Sprintf("\n\tKey: %X, Value: %X", key, value)
+ iter.Next()
+ }
+ t.Log(dumpStr)
+}
+
+// func (s *SimMachine) CheckoutVersion(t *rapid.T) {
+// if s.treeV1.Version() <= 1 {
+// // cannot checkout version 1 or lower
+// return
+// }
+// s.Commit(t) // make sure we've committed the current version before checking out a previous one
+// curVersion := s.treeV1.Version()
+// version := rapid.Int64Range(1, curVersion-1).Draw(t, "version")
+// itreeV1, err := s.treeV1.GetImmutable(version)
+// require.NoError(t, err, "failed to get immutable tree for V1 tree")
+// err = s.treeV2.LoadVersion(version)
+// require.NoError(t, err, "failed to load version in V2 tree")
+// defer require.NoError(t, s.treeV2.LoadVersion(curVersion), "failed to reload current version in V2 tree")
+//
+// s.debugDumpTree(t)
+//
+// s.compareIterators(t, nil, nil, true)
+// compareIteratorsAtVersion(t, itreeV1, s.treeV2, nil, nil, true)
+//}
+
+func (s *SimMachine) compareIterators(t *rapid.T, start, end []byte, ascending bool) {
+ iter1, err1 := s.treeV1.Iterator(start, end, ascending)
+ require.NoError(t, err1, "failed to create iterator for V1 tree")
+ iter2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Iterator(start, end)
+ compareIteratorsAtVersion(t, iter1, iter2)
+}
+
+func compareIteratorsAtVersion(t *rapid.T, iterV1, iterV2 corestore.Iterator) {
+ defer func() {
+ require.NoError(t, iterV1.Close(), "failed to close iterator for V1 tree")
+ }()
+ defer func() {
+ require.NoError(t, iterV2.Close(), "failed to close iterator for V2 tree")
+ }()
+
+ for {
+ hasNextV1 := iterV1.Valid()
+ hasNextV2 := iterV2.Valid()
+ require.Equal(t, hasNextV1, hasNextV2, "iterator validity mismatch between V1 and V2 trees")
+ if !hasNextV1 {
+ break
+ }
+ keyV1 := iterV1.Key()
+ valueV1 := iterV1.Value()
+ keyV2 := iterV2.Key()
+ valueV2 := iterV2.Value()
+ require.Equal(t, keyV1, keyV2, "key mismatch between V1 and V2 trees")
+ require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees")
+ iterV1.Next()
+ iterV2.Next()
+ }
+}
diff --git a/iavlx/update.go b/iavlx/update.go
new file mode 100644
index 000000000000..51342a50dbea
--- /dev/null
+++ b/iavlx/update.go
@@ -0,0 +1,37 @@
+package iavlx
+
+type KVUpdate struct {
+ SetNode *MemNode
+ DeleteKey []byte
+}
+
+type KVUpdateBatch struct {
+ Version uint32
+ Orphans [][]NodeID
+ Updates []KVUpdate
+}
+
+func NewKVUpdateBatch(stagedVersion uint32) *KVUpdateBatch {
+ return &KVUpdateBatch{
+ Version: stagedVersion,
+ }
+}
+
+type MutationContext struct {
+ Version uint32
+ Orphans []NodeID
+}
+
+func (ctx *MutationContext) MutateBranch(node Node) (*MemNode, error) {
+ id := node.ID()
+ if id != 0 {
+ ctx.Orphans = append(ctx.Orphans, id)
+ }
+ return node.MutateBranch(ctx.Version)
+}
+
+func (ctx *MutationContext) AddOrphan(id NodeID) {
+ if id != 0 {
+ ctx.Orphans = append(ctx.Orphans, id)
+ }
+}
diff --git a/iavlx/verify.go b/iavlx/verify.go
new file mode 100644
index 000000000000..25e012d75184
--- /dev/null
+++ b/iavlx/verify.go
@@ -0,0 +1,163 @@
+package iavlx
+
+import (
+ "bytes"
+ "fmt"
+)
+
+func VerifyTree(tree *CommitTree) error {
+ latest := tree.latest.Load()
+ if latest == nil {
+ return nil
+ }
+
+ return verifyNode(latest)
+}
+
+type DebugError struct {
+ Graph string
+ Err error
+}
+
+func (d *DebugError) Error() string {
+ return fmt.Sprintf("%v\nDOT graph:\n%s", d.Err, d.Graph)
+}
+
+func (d *DebugError) Unwrap() error {
+ return d.Err
+}
+
+var _ error = &DebugError{}
+
+// func verifyNodeDebug(np *NodePointer) error {
+// err := verifyNode(np)
+// if err != nil {
+// var dbgErr *DebugError
+// if errors.As(err, &dbgErr) {
+// return err
+// } else {
+// buf := &bytes.Buffer{}
+// err2 := RenderNodeDotGraph(buf, np)
+// if err2 == nil {
+// err = &DebugError{
+// Graph: buf.String(),
+// Err: err,
+// }
+// }
+// }
+// }
+// return err
+// }
+func verifyNode(np *NodePointer) error {
+ node, err := np.Resolve()
+ if err != nil {
+ return fmt.Errorf("resolve node %s: %w", np.id, err)
+ }
+
+ if node.Version() != uint32(np.id.Version()) {
+ return fmt.Errorf("node %s has version %d, expected %d", np.id, node.Version(), np.id.Version())
+ }
+
+ if node.IsLeaf() {
+ if node.Height() != 0 {
+ return fmt.Errorf("leaf node %s has height %d", np.id, node.Height())
+ }
+
+ if node.Size() != 1 {
+ return fmt.Errorf("leaf node %s has size %d, expected 1", np.id, node.Size())
+ }
+
+ if node.Left() != nil {
+ return fmt.Errorf("leaf node %s has non-nil left child", np.id)
+ }
+
+ if node.Right() != nil {
+ return fmt.Errorf("leaf node %s has non-nil right child", np.id)
+ }
+
+ hash, err := computeHash(node, nil, nil)
+ if err != nil {
+ return fmt.Errorf("compute hash for leaf node %s: %w", np.id, err)
+ }
+
+ if !bytes.Equal(hash, node.Hash()) {
+ return fmt.Errorf("leaf node %s has invalid hash", np.id)
+ }
+ } else {
+ leftPtr := node.Left()
+ if leftPtr == nil {
+ return fmt.Errorf("branch node %s has nil left child", np.id)
+ }
+
+ rightPtr := node.Right()
+ if rightPtr == nil {
+ return fmt.Errorf("branch node %s has nil right child", np.id)
+ }
+
+ left, err := leftPtr.Resolve()
+ if err != nil {
+ return fmt.Errorf("resolve left child of node %s: %w", np.id, err)
+ }
+
+ right, err := rightPtr.Resolve()
+ if err != nil {
+ return fmt.Errorf("resolve right child of node %s: %w", np.id, err)
+ }
+
+ key, err := node.Key()
+ if err != nil {
+ return fmt.Errorf("get key of node %s: %w", np.id, err)
+ }
+
+ leftKey, err := left.Key()
+ if err != nil {
+ return fmt.Errorf("get key of left child of node %s: %w", np.id, err)
+ }
+
+ rightKey, err := right.Key()
+ if err != nil {
+ return fmt.Errorf("get key of right child of node %s: %w", np.id, err)
+ }
+
+ if bytes.Compare(leftKey, key) >= 0 {
+ return fmt.Errorf("branch node %s with id %s has key %x, but left child %s, has key %x", node, np.id, key, left, leftKey)
+ }
+
+ if bytes.Compare(rightKey, key) < 0 {
+ return fmt.Errorf("branch node %s with id %s has key %x, but right child %s, has key %x", node, np.id, key, right, rightKey)
+ }
+
+ if left.Size()+right.Size() != node.Size() {
+ return fmt.Errorf("branch node %s has size %d, but children sizes are %d and %d", np.id, node.Size(), left.Size(), right.Size())
+ }
+
+ expectedHeight := maxUint8(left.Height(), right.Height()) + 1
+ if node.Height() != expectedHeight {
+ return fmt.Errorf("branch node %s has height %d, expected %d, left height %d, right height %d", np.id, node.Height(), expectedHeight, left.Height(), right.Height())
+ }
+
+ // ensure balanced
+ balance := int(left.Height()) - int(right.Height())
+ if balance < -1 || balance > 1 {
+ return fmt.Errorf("branch node %s is unbalanced: left height %d, right height %d", np.id, left.Height(), right.Height())
+ }
+
+ hash, err := computeHash(node, left.Hash(), right.Hash())
+ if err != nil {
+ return fmt.Errorf("compute hash for branch node %s: %w", np.id, err)
+ }
+
+ if !bytes.Equal(hash, node.Hash()) {
+ return fmt.Errorf("branch node %s has invalid hash", np.id)
+ }
+
+ if err := verifyNode(leftPtr); err != nil {
+ return err
+ }
+
+ if err := verifyNode(rightPtr); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/iavlx/version_info.go b/iavlx/version_info.go
new file mode 100644
index 000000000000..c98ae877d7ca
--- /dev/null
+++ b/iavlx/version_info.go
@@ -0,0 +1,27 @@
+package iavlx
+
+import (
+ "fmt"
+ "unsafe"
+)
+
+func init() {
+ if unsafe.Sizeof(VersionInfo{}) != VersionInfoSize {
+ panic(fmt.Sprintf("invalid VersionInfo size: got %d, want %d", unsafe.Sizeof(VersionInfo{}), VersionInfoSize))
+ }
+}
+
+const VersionInfoSize = 40
+
+type VersionInfo struct {
+ Leaves NodeSetInfo
+ Branches NodeSetInfo
+ RootID NodeID
+}
+
+type NodeSetInfo struct {
+ StartOffset uint32
+ Count uint32
+ StartIndex uint32
+ EndIndex uint32
+}
diff --git a/iavlx/writer.go b/iavlx/writer.go
new file mode 100644
index 000000000000..4c29e82a2b5d
--- /dev/null
+++ b/iavlx/writer.go
@@ -0,0 +1,62 @@
+package iavlx
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "unsafe"
+)
+
+type FileWriter struct {
+ writer *bufio.Writer
+ written int
+}
+
+func NewFileWriter(file *os.File) *FileWriter {
+ return &FileWriter{
+ writer: bufio.NewWriterSize(file, 512*1024 /* 512kb */), // TODO: maybe we can have this as a config option?
+ }
+}
+
+func (f *FileWriter) Write(p []byte) (n int, err error) {
+ n, err = f.writer.Write(p)
+ f.written += n
+ return n, err
+}
+
+func (f *FileWriter) Flush() error {
+ if err := f.writer.Flush(); err != nil {
+ return fmt.Errorf("failed to flush writer: %w", err)
+ }
+ return nil
+}
+
+func (f *FileWriter) Size() int {
+ return f.written
+}
+
+var _ io.Writer = (*FileWriter)(nil)
+
+type StructWriter[T any] struct {
+ size int
+ *FileWriter
+}
+
+func NewStructWriter[T any](file *os.File) *StructWriter[T] {
+ fw := NewFileWriter(file)
+
+ return &StructWriter[T]{
+ size: int(unsafe.Sizeof(*new(T))),
+ FileWriter: fw,
+ }
+}
+
+func (sw *StructWriter[T]) Append(x *T) error {
+ _, err := sw.Write(unsafe.Slice((*byte)(unsafe.Pointer(x)), sw.size))
+ return err
+}
+
+func (sw *StructWriter[T]) Count() int {
+ return sw.written / sw.size
+}
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000000..0702e8609b8f
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,13 @@
+[project]
+name = "cosmos-sdk"
+version = "0.1.0"
+description = "Add your description here"
+requires-python = ">=3.13"
+dependencies = [
+ "duckdb>=1.4.1",
+ "notebook>=7.4.7",
+ "pandas>=2.3.3",
+ "plotly>=6.4.0",
+ "pydantic>=2.12.3",
+ "pytz>=2025.2",
+]
diff --git a/server/api/server.go b/server/api/server.go
index 84de24c40784..5a107edd4a9f 100644
--- a/server/api/server.go
+++ b/server/api/server.go
@@ -2,7 +2,6 @@ package api
import (
"context"
- "fmt"
"net"
"net/http"
"strings"
@@ -24,7 +23,6 @@ import (
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/server/config"
cmtlogwrapper "github.com/cosmos/cosmos-sdk/server/log"
- "github.com/cosmos/cosmos-sdk/telemetry"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
)
@@ -35,7 +33,6 @@ type Server struct {
ClientCtx client.Context
GRPCSrv *grpc.Server
logger log.Logger
- metrics *telemetry.Metrics
// Start() is blocking and generally called from a separate goroutine.
// Close() can be called asynchronously and access shared memory
@@ -191,31 +188,6 @@ func (s *Server) Close() error {
return s.listener.Close()
}
-func (s *Server) SetTelemetry(m *telemetry.Metrics) {
- s.mtx.Lock()
- s.registerMetrics(m)
- s.mtx.Unlock()
-}
-
-func (s *Server) registerMetrics(m *telemetry.Metrics) {
- s.metrics = m
-
- metricsHandler := func(w http.ResponseWriter, r *http.Request) {
- format := strings.TrimSpace(r.FormValue("format"))
-
- gr, err := s.metrics.Gather(format)
- if err != nil {
- writeErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err))
- return
- }
-
- w.Header().Set("Content-Type", gr.ContentType)
- _, _ = w.Write(gr.Metrics)
- }
-
- s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET")
-}
-
// errorResponse defines the attributes of a JSON error response.
type errorResponse struct {
Code int `json:"code,omitempty"`
diff --git a/server/config/config.go b/server/config/config.go
index a50893e850c1..c9fa4351a087 100644
--- a/server/config/config.go
+++ b/server/config/config.go
@@ -8,7 +8,7 @@ import (
pruningtypes "cosmossdk.io/store/pruning/types"
- "github.com/cosmos/cosmos-sdk/telemetry"
+ _ "github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
@@ -190,13 +190,12 @@ type Config struct {
BaseConfig `mapstructure:",squash"`
// Telemetry defines the application telemetry configuration
- Telemetry telemetry.Config `mapstructure:"telemetry"`
- API APIConfig `mapstructure:"api"`
- GRPC GRPCConfig `mapstructure:"grpc"`
- GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"`
- StateSync StateSyncConfig `mapstructure:"state-sync"`
- Streaming StreamingConfig `mapstructure:"streaming"`
- Mempool MempoolConfig `mapstructure:"mempool"`
+ API APIConfig `mapstructure:"api"`
+ GRPC GRPCConfig `mapstructure:"grpc"`
+ GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"`
+ StateSync StateSyncConfig `mapstructure:"state-sync"`
+ Streaming StreamingConfig `mapstructure:"streaming"`
+ Mempool MempoolConfig `mapstructure:"mempool"`
}
// SetMinGasPrices sets the validator's minimum gas prices.
@@ -234,10 +233,6 @@ func DefaultConfig() *Config {
IAVLDisableFastNode: false,
AppDBBackend: "",
},
- Telemetry: telemetry.Config{
- Enabled: false,
- GlobalLabels: [][]string{},
- },
API: APIConfig{
Enable: false,
Swagger: false,
diff --git a/server/start.go b/server/start.go
index aab620b27548..0c321241c77b 100644
--- a/server/start.go
+++ b/server/start.go
@@ -76,6 +76,7 @@ const (
FlagIAVLCacheSize = "iavl-cache-size"
FlagDisableIAVLFastNode = "iavl-disable-fastnode"
FlagIAVLSyncPruning = "iavl-sync-pruning"
+ FlagIAVLXOptions = "iavlx-options"
FlagShutdownGrace = "shutdown-grace"
// state sync-related flags
@@ -230,20 +231,15 @@ func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreato
}
defer appCleanupFn()
- metrics, err := startTelemetry(svrCfg)
- if err != nil {
- return fmt.Errorf("failed to start telemetry: %w", err)
- }
-
emitServerInfoMetrics()
if !withCmt {
- return startStandAlone(svrCtx, svrCfg, clientCtx, app, metrics, opts)
+ return startStandAlone(svrCtx, svrCfg, clientCtx, app, opts)
}
- return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts)
+ return startInProcess(svrCtx, svrCfg, clientCtx, app, opts)
}
-func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, metrics *telemetry.Metrics, opts StartCmdOptions) error {
+func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, opts StartCmdOptions) error {
addr := svrCtx.Viper.GetString(flagAddress)
transport := svrCtx.Viper.GetString(flagTransport)
@@ -282,7 +278,7 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie
return err
}
- err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, svrCtx.Config.RootDir, grpcSrv, metrics)
+ err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, svrCtx.Config.RootDir, grpcSrv)
if err != nil {
return err
}
@@ -309,9 +305,7 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie
return g.Wait()
}
-func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application,
- metrics *telemetry.Metrics, opts StartCmdOptions,
-) error {
+func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, opts StartCmdOptions) error {
cmtCfg := svrCtx.Config
gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly)
@@ -348,7 +342,7 @@ func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clien
return fmt.Errorf("failed to start grpc server: %w", err)
}
- err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, cmtCfg.RootDir, grpcSrv, metrics)
+ err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, cmtCfg.RootDir, grpcSrv)
if err != nil {
return fmt.Errorf("failed to start api server: %w", err)
}
@@ -517,7 +511,6 @@ func startAPIServer(
app types.Application,
home string,
grpcSrv *grpc.Server,
- metrics *telemetry.Metrics,
) error {
if !svrCfg.API.Enable {
return nil
@@ -528,20 +521,12 @@ func startAPIServer(
apiSrv := api.New(clientCtx, svrCtx.Logger.With("module", "api-server"), grpcSrv)
app.RegisterAPIRoutes(apiSrv, svrCfg.API)
- if svrCfg.Telemetry.Enabled {
- apiSrv.SetTelemetry(metrics)
- }
-
g.Go(func() error {
return apiSrv.Start(ctx, svrCfg)
})
return nil
}
-func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) {
- return telemetry.New(cfg.Telemetry)
-}
-
// wrapCPUProfile starts CPU profiling, if enabled, and executes the provided
// callbackFn in a separate goroutine, then will wait for that callback to
// return.
@@ -623,6 +608,13 @@ func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions
cleanupFn = func() {
traceCleanupFn()
+
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if err := telemetry.Shutdown(shutdownCtx); err != nil {
+ svrCtx.Logger.Error("failed to shutdown telemetry", "error", err)
+ }
+
if localErr := app.Close(); localErr != nil {
svrCtx.Logger.Error(localErr.Error())
}
diff --git a/server/util.go b/server/util.go
index 42d01bd65d09..6c99aacf80b4 100644
--- a/server/util.go
+++ b/server/util.go
@@ -2,6 +2,7 @@ package server
import (
"context"
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -25,6 +26,7 @@ import (
"golang.org/x/sync/errgroup"
"cosmossdk.io/log"
+
"cosmossdk.io/store"
"cosmossdk.io/store/snapshots"
snapshottypes "cosmossdk.io/store/snapshots/types"
@@ -32,6 +34,7 @@ import (
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client/flags"
+ "github.com/cosmos/cosmos-sdk/iavlx"
"github.com/cosmos/cosmos-sdk/server/config"
"github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -582,6 +585,27 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) {
defaultMempool,
baseapp.SetChainID(chainID),
baseapp.SetQueryGasLimit(cast.ToUint64(appOpts.Get(FlagQueryGasLimit))),
+ func(bapp *baseapp.BaseApp) {
+ fmt.Println("Loading IAVLX as the commit multi-store...")
+ opts := &iavlx.Options{}
+ optsJson, ok := appOpts.Get(FlagIAVLXOptions).(string)
+ if ok && optsJson != "" {
+ err := json.Unmarshal([]byte(optsJson), opts)
+ if err != nil {
+ panic(fmt.Errorf("failed to unmarshal iavlx options: %w", err))
+ }
+ }
+
+ db, err := iavlx.LoadDB(
+ filepath.Join(homeDir, "data", "iavlx"),
+ opts,
+ bapp.Logger(),
+ )
+ if err != nil {
+ panic(fmt.Errorf("failed to load iavlx db: %w", err))
+ }
+ bapp.SetCMS(db)
+ },
}
}
diff --git a/simapp/app_di.go b/simapp/app_di.go
index 972edc75fa1a..3975847f8172 100644
--- a/simapp/app_di.go
+++ b/simapp/app_di.go
@@ -3,19 +3,25 @@
package simapp
import (
+ "encoding/json"
+ "fmt"
"io"
+ "os"
+ "path/filepath"
dbm "github.com/cosmos/cosmos-db"
clienthelpers "cosmossdk.io/client/v2/helpers"
"cosmossdk.io/depinject"
- "cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
+ "cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+ "github.com/cosmos/cosmos-sdk/iavlx"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/api"
@@ -100,6 +106,23 @@ func NewSimApp(
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *SimApp {
+ iavlxOpts, useIavlx := os.LookupEnv("IAVLX")
+ if useIavlx && iavlxOpts != "" {
+ fmt.Println("Setting up IAVLX as the underlying commit multi-store")
+ var opts iavlx.Options
+ err := json.Unmarshal([]byte(iavlxOpts), &opts)
+ if err != nil {
+ panic(err)
+ }
+ baseAppOptions = append(baseAppOptions, func(bApp *baseapp.BaseApp) {
+ dir := filepath.Join(appOpts.Get(flags.FlagHome).(string), "data", "iavlx")
+ db, err := iavlx.LoadDB(dir, &opts, logger)
+ if err != nil {
+ panic(err)
+ }
+ bApp.SetCMS(db)
+ })
+ }
var (
app = &SimApp{}
appBuilder *runtime.AppBuilder
diff --git a/simapp/go.mod b/simapp/go.mod
index ee826a142b4b..f800ac5b76fa 100644
--- a/simapp/go.mod
+++ b/simapp/go.mod
@@ -41,7 +41,6 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
- github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
@@ -72,6 +71,7 @@ require (
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
@@ -101,6 +101,8 @@ require (
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/emicklei/dot v1.8.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
@@ -114,6 +116,7 @@ require (
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
@@ -131,8 +134,10 @@ require (
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -157,6 +162,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.10.3 // indirect
+ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -172,16 +178,19 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.3 // indirect
- github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/prometheus/otlptranslator v0.0.2 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sasha-s/go-deadlock v0.3.6 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.7 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
@@ -190,21 +199,42 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect
+ github.com/tklauser/go-sysconf v0.3.15 // indirect
+ github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v1.0.1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect
+ go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
diff --git a/simapp/go.sum b/simapp/go.sum
index c2ccd1cd3d36..1f5d87ad05a0 100644
--- a/simapp/go.sum
+++ b/simapp/go.sum
@@ -55,7 +55,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -163,6 +162,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -285,6 +286,9 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI=
github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
@@ -349,6 +353,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
@@ -449,6 +456,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -457,6 +466,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow=
@@ -569,6 +580,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4=
github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -688,6 +701,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -714,6 +729,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
+github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -721,8 +738,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
-github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -747,6 +764,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
+github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -809,6 +828,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -822,6 +845,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw=
github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
@@ -837,25 +862,61 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw=
+go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8=
+go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
+go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
+go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
+go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -983,6 +1044,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1000,6 +1062,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1019,6 +1082,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/simapp/sim_test.go b/simapp/sim_test.go
index c76a12735d23..4f2bf6ad95f0 100644
--- a/simapp/sim_test.go
+++ b/simapp/sim_test.go
@@ -19,11 +19,13 @@ import (
"github.com/stretchr/testify/require"
"cosmossdk.io/log"
+
"cosmossdk.io/store"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
+ "github.com/cosmos/cosmos-sdk/telemetry"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sims "github.com/cosmos/cosmos-sdk/testutil/simsx"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -44,6 +46,10 @@ func init() {
flag.BoolVar(&FlagEnableStreamingValue, "EnableStreaming", false, "Enable streaming service")
}
+func TestMain(m *testing.M) {
+ telemetry.TestingMain(m, nil)
+}
+
// interBlockCacheOpt returns a BaseApp option function that sets the persistent
// inter-block write-through cache.
func interBlockCacheOpt() func(*baseapp.BaseApp) {
@@ -157,9 +163,10 @@ func IsEmptyValidatorSetErr(err error) bool {
}
func TestAppStateDeterminism(t *testing.T) {
- const numTimesToRunPerSeed = 3
+ cfg := simcli.NewConfigFromFlags()
+ numTimesToRunPerSeed := cfg.NumRuns
var seeds []int64
- if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue {
+ if s := cfg.Seed; s != simcli.DefaultSeedValue {
// We will be overriding the random seed and just run a single simulation on the provided seed value
for j := 0; j < numTimesToRunPerSeed; j++ { // multiple rounds
seeds = append(seeds, s)
diff --git a/systemtests/go.mod b/systemtests/go.mod
index f24a9631d68c..3de7a43cf36a 100644
--- a/systemtests/go.mod
+++ b/systemtests/go.mod
@@ -26,7 +26,6 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
- github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
@@ -34,6 +33,7 @@ require (
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cockroachdb/errors v1.12.0 // indirect
@@ -60,6 +60,8 @@ require (
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/emicklei/dot v1.8.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -70,6 +72,7 @@ require (
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
@@ -80,11 +83,14 @@ require (
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
+ github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
@@ -105,6 +111,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.10.3 // indirect
+ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
@@ -116,16 +123,19 @@ require (
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.3 // indirect
- github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/prometheus/otlptranslator v0.0.2 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sasha-s/go-deadlock v0.3.6 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.7 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -139,15 +149,38 @@ require (
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.15 // indirect
+ github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v1.0.1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect
+ go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
diff --git a/systemtests/go.sum b/systemtests/go.sum
index a39502487f05..8f6e036e1264 100644
--- a/systemtests/go.sum
+++ b/systemtests/go.sum
@@ -31,7 +31,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -91,6 +90,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -199,6 +200,9 @@ github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI=
github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
@@ -253,6 +257,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
@@ -346,6 +353,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -354,6 +363,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
@@ -456,6 +467,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4=
github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -572,6 +585,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -598,6 +613,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
+github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -605,8 +622,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
-github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -631,6 +648,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
+github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -697,6 +716,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -708,6 +731,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw=
github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
@@ -723,17 +748,55 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw=
+go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8=
+go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
+go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
+go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
+go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -846,6 +909,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -862,6 +926,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -880,6 +945,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/telemetry/README.md b/telemetry/README.md
new file mode 100644
index 000000000000..d1f873423b51
--- /dev/null
+++ b/telemetry/README.md
@@ -0,0 +1,65 @@
+## Quick Start For Local Telemetry
+
+To quickly setup a local telemetry environment where OpenTelemetry data is sent to a local instance of Grafana LGTM:
+1. start the [Grafana LGTM docker image](https://hub.docker.com/r/grafana/otel-lgtm):
+```shell
+docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm
+```
+2. create a basic OpenTelemetry configuration file which will send data to the local instance of Grafana LGTM:
+```yaml
+resource:
+ attributes:
+ - name: service.name
+ value: my_app_name
+tracer_provider:
+ processors:
+ - batch: # NOTE: you should use batch in production!
+ exporter:
+ otlp:
+ protocol: grpc
+ endpoint: http://localhost:4317
+meter_provider:
+ readers:
+ - periodic:
+ interval: 1000 # 1 second, maybe use something longer in production
+ exporter:
+ otlp:
+ protocol: grpc
+ endpoint: http://localhost:4317
+logger_provider:
+ processors:
+ - batch:
+ exporter:
+ otlp:
+ protocol: grpc
+ endpoint: http://localhost:4317
+```
+3. set the `OTEL_EXPERIMENTAL_CONFIG_FILE` environment variable to the path of the configuration file:
+`export OTEL_EXPERIMENTAL_CONFIG_FILE=path/to/config.yaml`
+4. start your application or tests
+5. view the data in Grafana LGTM at http://localhost:3000/. The Drilldown views are suggested for getting started.
+
+## OpenTelemetry Initialization
+
+While manual OpenTelemetry initialization is still supported, this package provides a single
+point of initialization such that end users can just use the official
+OpenTelemetry declarative configuration spec: https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/
+End users only need to set the `OTEL_EXPERIMENTAL_CONFIG_FILE` environment variable to the path of
+an OpenTelemetry configuration file and that's it.
+All the documentation necessary is provided in the OpenTelemetry documentation.
+
+## Developer Usage
+
+Developers need to do two things to use this package properly:
+ 1. Import this package before declaring any otel Tracer, Meter or Logger instances.
+ 2. Make sure Shutdown() is called when the application is shutting down.
+ Tests can use the TestingInit function at startup to accomplish this.
+
+If these steps are followed, developers can follow the official golang otel conventions
+of declaring package-level tracer and meter instances using otel.Tracer() and otel.Meter().
+NOTE: it is important to thread context.Context properly for spans, metrics, and logs to be
+correlated correctly.
+When using the SDK's context type, spans must be started with Context.StartSpan to
+get an SDK context which has the span set correctly.
+For logging, go.opentelemetry.io/contrib/bridges/otelslog provides a way to do this with the standard
+library slog package.
diff --git a/telemetry/config.go b/telemetry/config.go
new file mode 100644
index 000000000000..6dedddfeacd2
--- /dev/null
+++ b/telemetry/config.go
@@ -0,0 +1,216 @@
+package telemetry
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "os"
+ "time"
+
+ "go.opentelemetry.io/contrib/bridges/otelslog"
+ "go.opentelemetry.io/contrib/instrumentation/host"
+ "go.opentelemetry.io/contrib/instrumentation/runtime"
+ otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/exporters/stdout/stdoutlog"
+ "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
+ "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
+ logglobal "go.opentelemetry.io/otel/log/global"
+ logsdk "go.opentelemetry.io/otel/sdk/log"
+ metricsdk "go.opentelemetry.io/otel/sdk/metric"
+ tracesdk "go.opentelemetry.io/otel/sdk/trace"
+ "go.yaml.in/yaml/v3"
+)
+
+var sdk otelconf.SDK
+var shutdownFuncs []func(context.Context) error
+
+var isTelemetryEnabled = true
+
+func init() {
+ err := initOpenTelemetry()
+ if err != nil {
+ panic(err)
+ }
+}
+
+func initOpenTelemetry() error {
+ var err error
+
+ var opts []otelconf.ConfigurationOption
+
+ confFilename := os.Getenv("OTEL_EXPERIMENTAL_CONFIG_FILE")
+ if confFilename != "" {
+ bz, err := os.ReadFile(confFilename)
+ if err != nil {
+ return fmt.Errorf("failed to read telemetry config file: %w", err)
+ }
+
+ cfg, err := otelconf.ParseYAML(bz)
+ if err != nil {
+ return fmt.Errorf("failed to parse telemetry config file: %w", err)
+ }
+
+ cfgJson, err := json.Marshal(cfg)
+ if err != nil {
+ return fmt.Errorf("failed to marshal telemetry config file: %w", err)
+ }
+ fmt.Printf("\nInitializing telemetry with config:\n%s\n\n", cfgJson)
+
+ opts = append(opts, otelconf.WithOpenTelemetryConfiguration(*cfg))
+
+ // parse cosmos extra config
+ var extraCfg extraConfig
+ err = yaml.Unmarshal(bz, &extraCfg)
+ if err == nil {
+ if extraCfg.CosmosExtra != nil {
+ extra := *extraCfg.CosmosExtra
+ if extra.TraceFile != "" {
+ fmt.Printf("Initializing trace file: %s\n", extra.TraceFile)
+ traceFile, err := os.OpenFile(extra.TraceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
+ if err != nil {
+ return fmt.Errorf("failed to open trace file: %w", err)
+ }
+ shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error {
+ if err := traceFile.Close(); err != nil {
+ return fmt.Errorf("failed to close trace file: %w", err)
+ }
+ return nil
+ })
+ exporter, err := stdouttrace.New(
+ stdouttrace.WithWriter(traceFile),
+ //stdouttrace.WithPrettyPrint(),
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create stdout trace exporter: %w", err)
+ }
+ opts = append(opts, otelconf.WithTracerProviderOptions(
+ tracesdk.WithBatcher(exporter),
+ ))
+ }
+ if extra.MetricsFile != "" {
+ fmt.Printf("Initializing metrics file: %s\n", extra.MetricsFile)
+ metricsFile, err := os.OpenFile(extra.MetricsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
+ if err != nil {
+ return fmt.Errorf("failed to open metrics file: %w", err)
+ }
+ shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error {
+ if err := metricsFile.Close(); err != nil {
+ return fmt.Errorf("failed to close metrics file: %w", err)
+ }
+ return nil
+ })
+ exporter, err := stdoutmetric.New(
+ stdoutmetric.WithWriter(metricsFile),
+ //stdoutmetric.WithPrettyPrint(),
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create stdout metric exporter: %w", err)
+ }
+
+ // Configure periodic reader with custom interval if specified
+ readerOpts := []metricsdk.PeriodicReaderOption{}
+ if extra.MetricsFileInterval != "" {
+ interval, err := time.ParseDuration(extra.MetricsFileInterval)
+ if err != nil {
+ return fmt.Errorf("failed to parse metrics_file_interval: %w", err)
+ }
+ fmt.Printf("Configuring metrics export interval: %v\n", interval)
+ readerOpts = append(readerOpts, metricsdk.WithInterval(interval))
+ }
+
+ opts = append(opts, otelconf.WithMeterProviderOptions(
+ metricsdk.WithReader(metricsdk.NewPeriodicReader(exporter, readerOpts...)),
+ ))
+ }
+ if extra.LogsFile != "" {
+ fmt.Printf("Initializing logs file: %s\n", extra.LogsFile)
+ logsFile, err := os.OpenFile(extra.LogsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
+ if err != nil {
+ return fmt.Errorf("failed to open logs file: %w", err)
+ }
+ shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error {
+ if err := logsFile.Close(); err != nil {
+ return fmt.Errorf("failed to close logs file: %w", err)
+ }
+ return nil
+ })
+ exporter, err := stdoutlog.New(
+ stdoutlog.WithWriter(logsFile),
+ //stdoutlog.WithPrettyPrint(),
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create stdout log exporter: %w", err)
+ }
+ opts = append(opts, otelconf.WithLoggerProviderOptions(
+ logsdk.WithProcessor(logsdk.NewBatchProcessor(exporter)),
+ ))
+ }
+ if extra.InstrumentHost {
+ fmt.Println("Initializing host instrumentation")
+ if err := host.Start(); err != nil {
+ return fmt.Errorf("failed to start host instrumentation: %w", err)
+ }
+ }
+ if extra.InstrumentRuntime {
+ fmt.Println("Initializing runtime instrumentation")
+ if err := runtime.Start(); err != nil {
+ return fmt.Errorf("failed to start runtime instrumentation: %w", err)
+ }
+ }
+ }
+ } else {
+ fmt.Printf("failed to parse cosmos extra config: %v\n", err)
+ }
+ }
+
+ sdk, err = otelconf.NewSDK(opts...)
+ if err != nil {
+ return fmt.Errorf("failed to initialize telemetry: %w", err)
+ }
+
+ // setup otel global providers
+ otel.SetTracerProvider(sdk.TracerProvider())
+ otel.SetMeterProvider(sdk.MeterProvider())
+ logglobal.SetLoggerProvider(sdk.LoggerProvider())
+ // setup slog default provider so that any logs emitted the default slog will be traced
+ slog.SetDefault(otelslog.NewLogger("", otelslog.WithSource(true)))
+ // emit an initialized message which verifies basic telemetry is working
+ slog.Info("Telemetry initialized")
+ return nil
+}
+
+type extraConfig struct {
+ CosmosExtra *cosmosExtra `json:"cosmos_extra" yaml:"cosmos_extra" mapstructure:"cosmos_extra"`
+}
+
+type cosmosExtra struct {
+ TraceFile string `json:"trace_file" yaml:"trace_file" mapstructure:"trace_file"`
+ MetricsFile string `json:"metrics_file" yaml:"metrics_file" mapstructure:"metrics_file"`
+ MetricsFileInterval string `json:"metrics_file_interval" yaml:"metrics_file_interval" mapstructure:"metrics_file_interval"`
+ LogsFile string `json:"logs_file" yaml:"logs_file" mapstructure:"logs_file"`
+ InstrumentHost bool `json:"instrument_host" yaml:"instrument_host" mapstructure:"instrument_host"`
+ InstrumentRuntime bool `json:"instrument_runtime" yaml:"instrument_runtime" mapstructure:"instrument_runtime"`
+}
+
+func Shutdown(ctx context.Context) error {
+ err := sdk.Shutdown(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to shutdown telemetry: %w", err)
+ }
+ for _, f := range shutdownFuncs {
+ if err := f(ctx); err != nil {
+ return fmt.Errorf("failed to shutdown telemetry: %w", err)
+ }
+ }
+ return nil
+}
+
+func IsTelemetryEnabled() bool {
+ return isTelemetryEnabled
+}
+
+func SetTelemetryEnabled(v bool) {
+ isTelemetryEnabled = v
+}
diff --git a/telemetry/doc.go b/telemetry/doc.go
new file mode 100644
index 000000000000..606ace81fdd8
--- /dev/null
+++ b/telemetry/doc.go
@@ -0,0 +1,2 @@
+// Package telemetry initializes OpenTelemetry and provides legacy metrics wrapper functions.
+package telemetry
diff --git a/telemetry/metrics.go b/telemetry/metrics.go
deleted file mode 100644
index 67ace50c53ec..000000000000
--- a/telemetry/metrics.go
+++ /dev/null
@@ -1,241 +0,0 @@
-package telemetry
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "time"
-
- "github.com/hashicorp/go-metrics"
- "github.com/hashicorp/go-metrics/datadog"
- metricsprom "github.com/hashicorp/go-metrics/prometheus"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/common/expfmt"
-)
-
-// globalTelemetryEnabled is a private variable that stores the telemetry enabled state.
-// It is set on initialization and does not change for the lifetime of the program.
-var globalTelemetryEnabled bool
-
-// IsTelemetryEnabled provides controlled access to check if telemetry is enabled.
-func IsTelemetryEnabled() bool {
- return globalTelemetryEnabled
-}
-
-// EnableTelemetry allows for the global telemetry enabled state to be set.
-func EnableTelemetry() {
- globalTelemetryEnabled = true
-}
-
-// globalLabels defines the set of global labels that will be applied to all
-// metrics emitted using the telemetry package function wrappers.
-var globalLabels = []metrics.Label{}
-
-// Metrics supported format types.
-const (
- FormatDefault = ""
- FormatPrometheus = "prometheus"
- FormatText = "text"
- ContentTypeText = `text/plain; version=` + expfmt.TextVersion + `; charset=utf-8`
-
- MetricSinkInMem = "mem"
- MetricSinkStatsd = "statsd"
- MetricSinkDogsStatsd = "dogstatsd"
-)
-
-// DisplayableSink is an interface that defines a method for displaying metrics.
-type DisplayableSink interface {
- DisplayMetrics(resp http.ResponseWriter, req *http.Request) (any, error)
-}
-
-// Config defines the configuration options for application telemetry.
-type Config struct {
- // Prefixed with keys to separate services
- ServiceName string `mapstructure:"service-name"`
-
- // Enabled enables the application telemetry functionality. When enabled,
- // an in-memory sink is also enabled by default. Operators may also enabled
- // other sinks such as Prometheus.
- Enabled bool `mapstructure:"enabled"`
-
- // Enable prefixing gauge values with hostname
- EnableHostname bool `mapstructure:"enable-hostname"`
-
- // Enable adding hostname to labels
- EnableHostnameLabel bool `mapstructure:"enable-hostname-label"`
-
- // Enable adding service to labels
- EnableServiceLabel bool `mapstructure:"enable-service-label"`
-
- // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink.
- // It defines the retention duration in seconds.
- PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"`
-
- // GlobalLabels defines a global set of name/value label tuples applied to all
- // metrics emitted using the wrapper functions defined in telemetry package.
- //
- // Example:
- // [["chain_id", "cosmoshub-1"]]
- GlobalLabels [][]string `mapstructure:"global-labels"`
-
- // MetricsSink defines the type of metrics backend to use.
- MetricsSink string `mapstructure:"metrics-sink" default:"mem"`
-
- // StatsdAddr defines the address of a statsd server to send metrics to.
- // Only utilized if MetricsSink is set to "statsd" or "dogstatsd".
- StatsdAddr string `mapstructure:"statsd-addr"`
-
- // DatadogHostname defines the hostname to use when emitting metrics to
- // Datadog. Only utilized if MetricsSink is set to "dogstatsd".
- DatadogHostname string `mapstructure:"datadog-hostname"`
-}
-
-// Metrics defines a wrapper around application telemetry functionality. It allows
-// metrics to be gathered at any point in time. When creating a Metrics object,
-// internally, a global metrics is registered with a set of sinks as configured
-// by the operator. In addition to the sinks, when a process gets a SIGUSR1, a
-// dump of formatted recent metrics will be sent to STDERR.
-type Metrics struct {
- sink metrics.MetricSink
- prometheusEnabled bool
-}
-
-// GatherResponse is the response type of registered metrics
-type GatherResponse struct {
- Metrics []byte
- ContentType string
-}
-
-// New creates a new instance of Metrics
-func New(cfg Config) (_ *Metrics, rerr error) {
- globalTelemetryEnabled = cfg.Enabled
- if !cfg.Enabled {
- return nil, nil
- }
-
- if numGlobalLabels := len(cfg.GlobalLabels); numGlobalLabels > 0 {
- parsedGlobalLabels := make([]metrics.Label, numGlobalLabels)
- for i, gl := range cfg.GlobalLabels {
- parsedGlobalLabels[i] = NewLabel(gl[0], gl[1])
- }
- globalLabels = parsedGlobalLabels
- }
-
- metricsConf := metrics.DefaultConfig(cfg.ServiceName)
- metricsConf.EnableHostname = cfg.EnableHostname
- metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel
-
- var (
- sink metrics.MetricSink
- err error
- )
- switch cfg.MetricsSink {
- case MetricSinkStatsd:
- sink, err = metrics.NewStatsdSink(cfg.StatsdAddr)
- case MetricSinkDogsStatsd:
- sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname)
- default:
- memSink := metrics.NewInmemSink(10*time.Second, time.Minute)
- sink = memSink
- inMemSig := metrics.DefaultInmemSignal(memSink)
- defer func() {
- if rerr != nil {
- inMemSig.Stop()
- }
- }()
- }
-
- if err != nil {
- return nil, err
- }
-
- m := &Metrics{sink: sink}
- fanout := metrics.FanoutSink{sink}
-
- if cfg.PrometheusRetentionTime > 0 {
- m.prometheusEnabled = true
- prometheusOpts := metricsprom.PrometheusOpts{
- Expiration: time.Duration(cfg.PrometheusRetentionTime) * time.Second,
- }
-
- promSink, err := metricsprom.NewPrometheusSinkFrom(prometheusOpts)
- if err != nil {
- return nil, err
- }
-
- fanout = append(fanout, promSink)
- }
-
- if _, err := metrics.NewGlobal(metricsConf, fanout); err != nil {
- return nil, err
- }
-
- return m, nil
-}
-
-// Gather collects all registered metrics and returns a GatherResponse where the
-// metrics are encoded depending on the type. Metrics are either encoded via
-// Prometheus or JSON if in-memory.
-func (m *Metrics) Gather(format string) (GatherResponse, error) {
- switch format {
- case FormatPrometheus:
- return m.gatherPrometheus()
-
- case FormatText:
- return m.gatherGeneric()
-
- case FormatDefault:
- return m.gatherGeneric()
-
- default:
- return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format)
- }
-}
-
-// gatherPrometheus collects Prometheus metrics and returns a GatherResponse.
-// If Prometheus metrics are not enabled, it returns an error.
-func (m *Metrics) gatherPrometheus() (GatherResponse, error) {
- if !m.prometheusEnabled {
- return GatherResponse{}, errors.New("prometheus metrics are not enabled")
- }
-
- metricsFamilies, err := prometheus.DefaultGatherer.Gather()
- if err != nil {
- return GatherResponse{}, fmt.Errorf("failed to gather prometheus metrics: %w", err)
- }
-
- buf := &bytes.Buffer{}
- defer buf.Reset()
-
- e := expfmt.NewEncoder(buf, expfmt.NewFormat(expfmt.TypeTextPlain))
-
- for _, mf := range metricsFamilies {
- if err := e.Encode(mf); err != nil {
- return GatherResponse{}, fmt.Errorf("failed to encode prometheus metrics: %w", err)
- }
- }
-
- return GatherResponse{ContentType: ContentTypeText, Metrics: buf.Bytes()}, nil
-}
-
-// gatherGeneric collects generic metrics and returns a GatherResponse.
-func (m *Metrics) gatherGeneric() (GatherResponse, error) {
- gm, ok := m.sink.(DisplayableSink)
- if !ok {
- return GatherResponse{}, errors.New("non in-memory metrics sink does not support generic format")
- }
-
- summary, err := gm.DisplayMetrics(nil, nil)
- if err != nil {
- return GatherResponse{}, fmt.Errorf("failed to gather in-memory metrics: %w", err)
- }
-
- content, err := json.Marshal(summary)
- if err != nil {
- return GatherResponse{}, fmt.Errorf("failed to encode in-memory metrics: %w", err)
- }
-
- return GatherResponse{ContentType: "application/json", Metrics: content}, nil
-}
diff --git a/telemetry/metrics_test.go b/telemetry/metrics_test.go
deleted file mode 100644
index b741f007db22..000000000000
--- a/telemetry/metrics_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package telemetry
-
-import (
- "encoding/json"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/go-metrics"
- "github.com/stretchr/testify/require"
-)
-
-func TestMetrics_Disabled(t *testing.T) {
- m, err := New(Config{Enabled: false})
- require.Nil(t, m)
- require.Nil(t, err)
-}
-
-func TestMetrics_InMem(t *testing.T) {
- m, err := New(Config{
- MetricsSink: MetricSinkInMem,
- Enabled: true,
- EnableHostname: false,
- ServiceName: "test",
- })
- require.NoError(t, err)
- require.NotNil(t, m)
-
- emitMetrics()
-
- gr, err := m.Gather(FormatText)
- require.NoError(t, err)
- require.Equal(t, gr.ContentType, "application/json")
-
- jsonMetrics := make(map[string]any)
- require.NoError(t, json.Unmarshal(gr.Metrics, &jsonMetrics))
-
- counters := jsonMetrics["Counters"].([]any)
- require.Equal(t, counters[0].(map[string]any)["Count"].(float64), 10.0)
- require.Equal(t, counters[0].(map[string]any)["Name"].(string), "test.dummy_counter")
-}
-
-func TestMetrics_Prom(t *testing.T) {
- m, err := New(Config{
- MetricsSink: MetricSinkInMem,
- Enabled: true,
- EnableHostname: false,
- ServiceName: "test",
- PrometheusRetentionTime: 60,
- EnableHostnameLabel: false,
- })
- require.NoError(t, err)
- require.NotNil(t, m)
- require.True(t, m.prometheusEnabled)
-
- emitMetrics()
-
- gr, err := m.Gather(FormatPrometheus)
- require.NoError(t, err)
- require.Equal(t, gr.ContentType, string(ContentTypeText))
-
- require.True(t, strings.Contains(string(gr.Metrics), "test_dummy_counter 30"))
-}
-
-func emitMetrics() {
- ticker := time.NewTicker(time.Second)
- timeout := time.After(30 * time.Second)
-
- for {
- select {
- case <-ticker.C:
- metrics.IncrCounter([]string{"dummy_counter"}, 1.0)
- case <-timeout:
- return
- }
- }
-}
diff --git a/telemetry/testing.go b/telemetry/testing.go
new file mode 100644
index 000000000000..623a7e93e7cf
--- /dev/null
+++ b/telemetry/testing.go
@@ -0,0 +1,27 @@
+package telemetry
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+)
+
+// TestingMain should be used in tests where you want to run telemetry and need clean shutdown
+// behavior at the end of the test, for instance to collect benchmark metrics.
+// If ctx is nil, context.Background() is used.
+// Example:
+//
+// func TestMain(m *testing.M) {
+// telemetry.TestingMain(m, nil)
+// }
+func TestingMain(m *testing.M, ctx context.Context) {
+ code := m.Run()
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ if err := Shutdown(ctx); err != nil {
+ fmt.Printf("failed to shutdown telemetry after test completion: %v\n", err)
+ }
+ os.Exit(code)
+}
diff --git a/telemetry/wrapper.go b/telemetry/wrapper.go
index 8445ed823830..1d28e8af4d73 100644
--- a/telemetry/wrapper.go
+++ b/telemetry/wrapper.go
@@ -1,11 +1,16 @@
package telemetry
import (
+ "sync"
"time"
"github.com/hashicorp/go-metrics"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/metric"
)
+var globalLabels []metrics.Label
+
// Common metric key constants
const (
MetricKeyPreBlocker = "pre_blocker"
@@ -14,6 +19,17 @@ const (
MetricLabelNameModule = "module"
)
+var meter = otel.Meter("cosmos-sdk")
+var mtx sync.RWMutex
+var counters map[string]metric.Float64Counter
+var gauges map[string]metric.Float64Gauge
+var histograms map[string]metric.Float64Histogram
+
+type Label struct {
+ Name string
+ Value string
+}
+
// NewLabel creates a new instance of Label with name and value
func NewLabel(name, value string) metrics.Label {
return metrics.Label{Name: name, Value: value}
diff --git a/telemetry/wrapper_test.go b/telemetry/wrapper_test.go
deleted file mode 100644
index 5388839874bc..000000000000
--- a/telemetry/wrapper_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package telemetry
-
-import (
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-var mu sync.Mutex
-
-func initTelemetry(v bool) {
- globalTelemetryEnabled = v
-}
-
-// Reset the global state to a known disabled state before each test.
-func setupTest(t *testing.T) {
- t.Helper()
- mu.Lock() // Ensure no other test can modify global state at the same time.
- defer mu.Unlock()
- initTelemetry(false)
-}
-
-// TestNow tests the Now function when telemetry is enabled and disabled.
-func TestNow(t *testing.T) {
- setupTest(t) // Locks the mutex to avoid race condition.
-
- initTelemetry(true)
- telemetryTime := Now()
- assert.NotEqual(t, time.Time{}, telemetryTime, "Now() should not return zero time when telemetry is enabled")
-
- setupTest(t) // Reset the global state and lock the mutex again.
-
- initTelemetry(false)
- telemetryTime = Now()
- assert.Equal(t, time.Time{}, telemetryTime, "Now() should return zero time when telemetry is disabled")
-}
-
-// TestIsTelemetryEnabled tests the IsTelemetryEnabled function.
-func TestIsTelemetryEnabled(t *testing.T) {
- setupTest(t) // Locks the mutex to avoid race condition.
-
- initTelemetry(true)
- assert.True(t, IsTelemetryEnabled(), "IsTelemetryEnabled() should return true when globalTelemetryEnabled is set to true")
-
- setupTest(t) // Reset the global state and lock the mutex again.
-
- initTelemetry(false)
- assert.False(t, IsTelemetryEnabled(), "IsTelemetryEnabled() should return false when globalTelemetryEnabled is set to false")
-}
diff --git a/tests/go.mod b/tests/go.mod
index 9333bd85efb2..43bac5a8fa8a 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -44,7 +44,6 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
- github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
@@ -75,6 +74,7 @@ require (
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
@@ -100,6 +100,8 @@ require (
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/emicklei/dot v1.8.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
@@ -113,6 +115,7 @@ require (
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
@@ -130,8 +133,10 @@ require (
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -156,6 +161,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.10.3 // indirect
+ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -170,16 +176,19 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.3 // indirect
- github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/prometheus/otlptranslator v0.0.2 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sasha-s/go-deadlock v0.3.6 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.7 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -190,21 +199,42 @@ require (
github.com/supranational/blst v0.3.16 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tidwall/btree v1.8.1 // indirect
+ github.com/tklauser/go-sysconf v0.3.15 // indirect
+ github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v1.0.1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect
+ go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
diff --git a/tests/go.sum b/tests/go.sum
index 6d3701117192..42261c9ec25f 100644
--- a/tests/go.sum
+++ b/tests/go.sum
@@ -53,7 +53,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -161,6 +160,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -277,6 +278,9 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI=
github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
@@ -342,6 +346,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
@@ -444,6 +451,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -452,6 +461,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow=
@@ -565,6 +576,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4=
github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -687,6 +700,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -713,6 +728,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
+github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -720,8 +737,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
-github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -746,6 +763,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
+github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -808,6 +827,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -821,6 +844,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw=
github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
@@ -836,25 +861,61 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw=
+go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8=
+go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
+go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
+go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
+go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -983,6 +1044,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -999,6 +1061,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1020,6 +1083,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod
index 9e5b9858dcc5..58744d71760c 100644
--- a/tests/systemtests/go.mod
+++ b/tests/systemtests/go.mod
@@ -30,7 +30,6 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
- github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
@@ -38,6 +37,7 @@ require (
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cockroachdb/errors v1.12.0 // indirect
@@ -66,6 +66,8 @@ require (
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/emicklei/dot v1.8.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -76,6 +78,7 @@ require (
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
@@ -86,11 +89,14 @@ require (
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
+ github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
@@ -111,6 +117,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.10.3 // indirect
+ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
@@ -122,16 +129,19 @@ require (
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.3 // indirect
- github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/prometheus/otlptranslator v0.0.2 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sasha-s/go-deadlock v0.3.6 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.7 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -145,15 +155,38 @@ require (
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.15 // indirect
+ github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v1.0.1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect
+ go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.38.0 // indirect
+ go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum
index a1ce60b13e62..ed428bc46a3b 100644
--- a/tests/systemtests/go.sum
+++ b/tests/systemtests/go.sum
@@ -29,7 +29,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -89,6 +88,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -197,6 +198,9 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI=
github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
@@ -251,6 +255,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
@@ -344,6 +351,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
+github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -352,6 +361,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
@@ -454,6 +465,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4=
github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
+github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -570,6 +583,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -596,6 +611,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
+github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -603,8 +620,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
-github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -629,6 +646,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
+github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -695,6 +714,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -706,6 +729,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw=
github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
@@ -721,17 +746,55 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
+go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ=
+go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ=
+go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw=
+go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8=
+go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
+go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
+go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
+go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
+go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
+go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -844,6 +907,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -860,6 +924,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -878,6 +943,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/testutil/network/network.go b/testutil/network/network.go
index 138b94bf77de..d4a0dfc9efee 100644
--- a/testutil/network/network.go
+++ b/testutil/network/network.go
@@ -25,11 +25,12 @@ import (
"google.golang.org/grpc"
"cosmossdk.io/depinject"
- "cosmossdk.io/log"
sdkmath "cosmossdk.io/math"
"cosmossdk.io/math/unsafe"
pruningtypes "cosmossdk.io/store/pruning/types"
+ "cosmossdk.io/log"
+
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
@@ -377,7 +378,6 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) {
appCfg.MinGasPrices = cfg.MinGasPrices
appCfg.API.Enable = true
appCfg.API.Swagger = false
- appCfg.Telemetry.Enabled = false
ctx := server.NewDefaultContext()
cmtCfg := ctx.Config
diff --git a/types/config.go b/types/config.go
index 1c61968bb06b..e4ef476e8e31 100644
--- a/types/config.go
+++ b/types/config.go
@@ -31,6 +31,7 @@ type Config struct {
var (
configRegistry = make(map[string]*Config)
registryMutex sync.Mutex
+ configKey string
)
// getConfigKey returns a unique config scope identifier.
@@ -40,6 +41,10 @@ func getConfigKey() string {
return id
}
+ if configKey != "" {
+ return configKey
+ }
+
exe, errExec := os.Executable()
host, errHost := os.Hostname()
pid := os.Getpid()
@@ -51,7 +56,8 @@ func getConfigKey() string {
host = "unknown-host"
}
- return fmt.Sprintf("%s|%s|%d", host, exe, pid)
+ configKey = fmt.Sprintf("%s|%s|%d", host, exe, pid)
+ return configKey
}
// NewConfig returns a new Config with default values.
diff --git a/types/context.go b/types/context.go
index 4e762cc15943..34f4a85cfd2f 100644
--- a/types/context.go
+++ b/types/context.go
@@ -6,12 +6,14 @@ import (
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
+ "go.opentelemetry.io/otel/trace"
"cosmossdk.io/core/comet"
"cosmossdk.io/core/header"
- "cosmossdk.io/log"
"cosmossdk.io/store/gaskv"
storetypes "cosmossdk.io/store/types"
+
+ "cosmossdk.io/log"
)
// ExecMode defines the execution mode which can be set on a Context.
@@ -439,6 +441,14 @@ func (c Context) WithIncarnationCache(cache map[string]any) Context {
return c
}
+// StartSpan starts an otel span and returns a new context with the span attached.
+// Use this instead of calling tracer.Start directly to have the span correctly
+// attached to this context type.
+func (c Context) StartSpan(tracer trace.Tracer, spanName string, opts ...trace.SpanStartOption) (Context, trace.Span) {
+ goCtx, span := tracer.Start(c.baseCtx, spanName, opts...)
+ return c.WithContext(goCtx), span
+}
+
var (
_ context.Context = Context{}
_ storetypes.Context = Context{}
diff --git a/types/module/module.go b/types/module/module.go
index b3b3cccb3774..457c6444ab74 100644
--- a/types/module/module.go
+++ b/types/module/module.go
@@ -39,6 +39,8 @@ import (
abci "github.com/cometbft/cometbft/abci/types"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/trace"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/genesis"
@@ -52,6 +54,10 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
+var (
+ tracer = otel.Tracer("cosmos-sdk/types/module")
+)
+
// AppModuleBasic is the standard form for basic non-dependent elements of an application module.
type AppModuleBasic interface {
HasName
@@ -482,6 +488,10 @@ func (m *Manager) RegisterServices(cfg Configurator) error {
// module must return a non-empty validator set update to correctly initialize
// the chain.
func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) (*abci.ResponseInitChain, error) {
+ var span trace.Span
+ ctx, span = ctx.StartSpan(tracer, "Manager.InitGenesis")
+ defer span.End()
+
var validatorUpdates []abci.ValidatorUpdate
ctx.Logger().Info("initializing blockchain state from genesis.json")
for _, moduleName := range m.OrderInitGenesis {
@@ -489,26 +499,27 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData
continue
}
+ modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("InitGenesis.%s", moduleName))
mod := m.Modules[moduleName]
// we might get an adapted module, a native core API module or a legacy module
if module, ok := mod.(appmodule.HasGenesis); ok {
- ctx.Logger().Debug("running initialization for module", "module", moduleName)
+ modCtx.Logger().Debug("running initialization for module", "module", moduleName)
// core API genesis
source, err := genesis.SourceFromRawJSON(genesisData[moduleName])
if err != nil {
return &abci.ResponseInitChain{}, err
}
- err = module.InitGenesis(ctx, source)
+ err = module.InitGenesis(modCtx, source)
if err != nil {
return &abci.ResponseInitChain{}, err
}
} else if module, ok := mod.(HasGenesis); ok {
- ctx.Logger().Debug("running initialization for module", "module", moduleName)
- module.InitGenesis(ctx, cdc, genesisData[moduleName])
+ modCtx.Logger().Debug("running initialization for module", "module", moduleName)
+ module.InitGenesis(modCtx, cdc, genesisData[moduleName])
} else if module, ok := mod.(HasABCIGenesis); ok {
- ctx.Logger().Debug("running initialization for module", "module", moduleName)
- moduleValUpdates := module.InitGenesis(ctx, cdc, genesisData[moduleName])
+ modCtx.Logger().Debug("running initialization for module", "module", moduleName)
+ moduleValUpdates := module.InitGenesis(modCtx, cdc, genesisData[moduleName])
// use these validator updates if provided, the module manager assumes
// only one module will update the validator set
@@ -519,6 +530,7 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData
validatorUpdates = moduleValUpdates
}
}
+ modSpan.End()
}
// a chain must initialize with a non-empty validator set
@@ -754,10 +766,15 @@ func (m Manager) RunMigrations(ctx context.Context, cfg Configurator, fromVM Ver
// It takes the current context as a parameter and returns a boolean value
// indicating whether the migration was successfully executed or not.
func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) {
+ var span trace.Span
+ ctx, span = ctx.StartSpan(tracer, "Manager.PreBlock")
+ defer span.End()
+
paramsChanged := false
for _, moduleName := range m.OrderPreBlockers {
+ modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("PreBlock.%s", moduleName))
if module, ok := m.Modules[moduleName].(appmodule.HasPreBlocker); ok {
- rsp, err := module.PreBlock(ctx)
+ rsp, err := module.PreBlock(modCtx)
if err != nil {
return nil, err
}
@@ -765,6 +782,7 @@ func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) {
paramsChanged = true
}
}
+ modSpan.End()
}
return &sdk.ResponsePreBlock{
ConsensusParamsChanged: paramsChanged,
@@ -775,13 +793,20 @@ func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) {
// child context with an event manager to aggregate events emitted from all
// modules.
func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) {
+ var span trace.Span
+ ctx, span = ctx.StartSpan(tracer, "Manager.BeginBlock")
+ defer span.End()
+
ctx = ctx.WithEventManager(sdk.NewEventManager())
for _, moduleName := range m.OrderBeginBlockers {
+ var modSpan trace.Span
+ modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("BeginBlock.%s", moduleName))
if module, ok := m.Modules[moduleName].(appmodule.HasBeginBlocker); ok {
- if err := module.BeginBlock(ctx); err != nil {
+ if err := module.BeginBlock(modCtx); err != nil {
return sdk.BeginBlock{}, err
}
}
+ modSpan.End()
}
return sdk.BeginBlock{
@@ -793,17 +818,22 @@ func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) {
// child context with an event manager to aggregate events emitted from all
// modules.
func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) {
+ var span trace.Span
+ ctx, span = ctx.StartSpan(tracer, "Manager.EndBlock")
+ defer span.End()
+
ctx = ctx.WithEventManager(sdk.NewEventManager())
validatorUpdates := []abci.ValidatorUpdate{}
for _, moduleName := range m.OrderEndBlockers {
+ modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("EndBlock.%s", moduleName))
if module, ok := m.Modules[moduleName].(appmodule.HasEndBlocker); ok {
- err := module.EndBlock(ctx)
+ err := module.EndBlock(modCtx)
if err != nil {
return sdk.EndBlock{}, err
}
} else if module, ok := m.Modules[moduleName].(HasABCIEndBlock); ok {
- moduleValUpdates, err := module.EndBlock(ctx)
+ moduleValUpdates, err := module.EndBlock(modCtx)
if err != nil {
return sdk.EndBlock{}, err
}
@@ -821,6 +851,7 @@ func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) {
} else {
continue
}
+ modSpan.End()
}
return sdk.EndBlock{
diff --git a/types/simulation/config.go b/types/simulation/config.go
index 1e385fdfa1d6..b903ed3bc1f7 100644
--- a/types/simulation/config.go
+++ b/types/simulation/config.go
@@ -16,6 +16,7 @@ type Config struct {
InitialBlockHeight int // initial block to start the simulation
GenesisTime int64 // genesis time to start the simulation
NumBlocks int // number of new blocks to simulate from the initial block height
+ NumRuns int // number of times to run the simulation for simulations that have multiple runs
BlockSize int // operations per block
ChainID string // chain-id used on the simulation
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 000000000000..07704c9c62b0
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,1650 @@
+version = 1
+revision = 2
+requires-python = ">=3.13"
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version < '3.14'",
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "sniffio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
+]
+
+[[package]]
+name = "appnope"
+version = "0.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" },
+]
+
+[[package]]
+name = "argon2-cffi"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "argon2-cffi-bindings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
+]
+
+[[package]]
+name = "argon2-cffi-bindings"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" },
+ { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" },
+ { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" },
+ { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
+ { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
+ { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
+ { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
+ { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
+]
+
+[[package]]
+name = "arrow"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+ { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" },
+]
+
+[[package]]
+name = "asttokens"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" },
+]
+
+[[package]]
+name = "async-lru"
+version = "2.0.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
+]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.14.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "soupsieve" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" },
+]
+
+[[package]]
+name = "bleach"
+version = "6.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" },
+]
+
+[package.optional-dependencies]
+css = [
+ { name = "tinycss2" },
+]
+
+[[package]]
+name = "certifi"
+version = "2025.10.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
+ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
+ { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
+ { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
+ { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
+ { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
+ { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
+ { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
+ { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
+ { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
+ { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
+ { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
+ { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "comm"
+version = "0.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" },
+]
+
+[[package]]
+name = "cosmos-sdk"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "duckdb" },
+ { name = "notebook" },
+ { name = "pandas" },
+ { name = "plotly" },
+ { name = "pydantic" },
+ { name = "pytz" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "duckdb", specifier = ">=1.4.1" },
+ { name = "notebook", specifier = ">=7.4.7" },
+ { name = "pandas", specifier = ">=2.3.3" },
+ { name = "plotly", specifier = ">=6.4.0" },
+ { name = "pydantic", specifier = ">=2.12.3" },
+ { name = "pytz", specifier = ">=2025.2" },
+]
+
+[[package]]
+name = "debugpy"
+version = "1.8.17"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386, upload-time = "2025-09-17T16:33:54.594Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100, upload-time = "2025-09-17T16:33:56.353Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002, upload-time = "2025-09-17T16:33:58.231Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047, upload-time = "2025-09-17T16:34:00.586Z" },
+ { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899, upload-time = "2025-09-17T16:34:02.657Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254, upload-time = "2025-09-17T16:34:04.486Z" },
+ { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203, upload-time = "2025-09-17T16:34:06.65Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493, upload-time = "2025-09-17T16:34:08.483Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" },
+]
+
+[[package]]
+name = "decorator"
+version = "5.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
+]
+
+[[package]]
+name = "duckdb"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ea/e7/21cf50a3d52ffceee1f0bcc3997fa96a5062e6bab705baee4f6c4e33cce5/duckdb-1.4.1.tar.gz", hash = "sha256:f903882f045d057ebccad12ac69975952832edfe133697694854bb784b8d6c76", size = 18461687, upload-time = "2025-10-07T10:37:28.605Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/08/705988c33e38665c969f7876b3ca4328be578554aa7e3dc0f34158da3e64/duckdb-1.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:46496a2518752ae0c6c5d75d4cdecf56ea23dd098746391176dd8e42cf157791", size = 29077070, upload-time = "2025-10-07T10:36:59.83Z" },
+ { url = "https://files.pythonhosted.org/packages/99/c5/7c9165f1e6b9069441bcda4da1e19382d4a2357783d37ff9ae238c5c41ac/duckdb-1.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1c65ae7e9b541cea07d8075343bcfebdecc29a3c0481aa6078ee63d51951cfcd", size = 16167506, upload-time = "2025-10-07T10:37:02.24Z" },
+ { url = "https://files.pythonhosted.org/packages/38/46/267f4a570a0ee3ae6871ddc03435f9942884284e22a7ba9b7cb252ee69b6/duckdb-1.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:598d1a314e34b65d9399ddd066ccce1eeab6a60a2ef5885a84ce5ed62dbaf729", size = 13762330, upload-time = "2025-10-07T10:37:04.581Z" },
+ { url = "https://files.pythonhosted.org/packages/15/7b/c4f272a40c36d82df20937d93a1780eb39ab0107fe42b62cba889151eab9/duckdb-1.4.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2f16b8def782d484a9f035fc422bb6f06941ed0054b4511ddcdc514a7fb6a75", size = 18504687, upload-time = "2025-10-07T10:37:06.991Z" },
+ { url = "https://files.pythonhosted.org/packages/17/fc/9b958751f0116d7b0406406b07fa6f5a10c22d699be27826d0b896f9bf51/duckdb-1.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a7d0aed068a5c33622a8848857947cab5cfb3f2a315b1251849bac2c74c492", size = 20513823, upload-time = "2025-10-07T10:37:09.349Z" },
+ { url = "https://files.pythonhosted.org/packages/30/79/4f544d73fcc0513b71296cb3ebb28a227d22e80dec27204977039b9fa875/duckdb-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:280fd663dacdd12bb3c3bf41f3e5b2e5b95e00b88120afabb8b8befa5f335c6f", size = 12336460, upload-time = "2025-10-07T10:37:12.154Z" },
+]
+
+[[package]]
+name = "executing"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
+]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.21.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" },
+]
+
+[[package]]
+name = "fqdn"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "ipykernel"
+version = "7.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "appnope", marker = "sys_platform == 'darwin'" },
+ { name = "comm" },
+ { name = "debugpy" },
+ { name = "ipython" },
+ { name = "jupyter-client" },
+ { name = "jupyter-core" },
+ { name = "matplotlib-inline" },
+ { name = "nest-asyncio" },
+ { name = "packaging" },
+ { name = "psutil" },
+ { name = "pyzmq" },
+ { name = "tornado" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" },
+]
+
+[[package]]
+name = "ipython"
+version = "9.6.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "decorator" },
+ { name = "ipython-pygments-lexers" },
+ { name = "jedi" },
+ { name = "matplotlib-inline" },
+ { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
+ { name = "prompt-toolkit" },
+ { name = "pygments" },
+ { name = "stack-data" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2a/34/29b18c62e39ee2f7a6a3bba7efd952729d8aadd45ca17efc34453b717665/ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731", size = 4396932, upload-time = "2025-09-29T10:55:53.948Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196", size = 616170, upload-time = "2025-09-29T10:55:47.676Z" },
+]
+
+[[package]]
+name = "ipython-pygments-lexers"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
+]
+
+[[package]]
+name = "isoduration"
+version = "20.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "arrow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
+]
+
+[[package]]
+name = "jedi"
+version = "0.19.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "parso" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "json5"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" },
+]
+
+[[package]]
+name = "jsonpointer"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.25.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "jsonschema-specifications" },
+ { name = "referencing" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
+]
+
+[package.optional-dependencies]
+format-nongpl = [
+ { name = "fqdn" },
+ { name = "idna" },
+ { name = "isoduration" },
+ { name = "jsonpointer" },
+ { name = "rfc3339-validator" },
+ { name = "rfc3986-validator" },
+ { name = "rfc3987-syntax" },
+ { name = "uri-template" },
+ { name = "webcolors" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "jupyter-client"
+version = "8.6.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jupyter-core" },
+ { name = "python-dateutil" },
+ { name = "pyzmq" },
+ { name = "tornado" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" },
+]
+
+[[package]]
+name = "jupyter-core"
+version = "5.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "platformdirs" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" },
+]
+
+[[package]]
+name = "jupyter-events"
+version = "0.12.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jsonschema", extra = ["format-nongpl"] },
+ { name = "packaging" },
+ { name = "python-json-logger" },
+ { name = "pyyaml" },
+ { name = "referencing" },
+ { name = "rfc3339-validator" },
+ { name = "rfc3986-validator" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" },
+]
+
+[[package]]
+name = "jupyter-lsp"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jupyter-server" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" },
+]
+
+[[package]]
+name = "jupyter-server"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "argon2-cffi" },
+ { name = "jinja2" },
+ { name = "jupyter-client" },
+ { name = "jupyter-core" },
+ { name = "jupyter-events" },
+ { name = "jupyter-server-terminals" },
+ { name = "nbconvert" },
+ { name = "nbformat" },
+ { name = "packaging" },
+ { name = "prometheus-client" },
+ { name = "pywinpty", marker = "os_name == 'nt'" },
+ { name = "pyzmq" },
+ { name = "send2trash" },
+ { name = "terminado" },
+ { name = "tornado" },
+ { name = "traitlets" },
+ { name = "websocket-client" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" },
+]
+
+[[package]]
+name = "jupyter-server-terminals"
+version = "0.5.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pywinpty", marker = "os_name == 'nt'" },
+ { name = "terminado" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" },
+]
+
+[[package]]
+name = "jupyterlab"
+version = "4.4.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "async-lru" },
+ { name = "httpx" },
+ { name = "ipykernel" },
+ { name = "jinja2" },
+ { name = "jupyter-core" },
+ { name = "jupyter-lsp" },
+ { name = "jupyter-server" },
+ { name = "jupyterlab-server" },
+ { name = "notebook-shim" },
+ { name = "packaging" },
+ { name = "setuptools" },
+ { name = "tornado" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6a/5d/75c42a48ff5fc826a7dff3fe4004cda47c54f9d981c351efacfbc9139d3c/jupyterlab-4.4.10.tar.gz", hash = "sha256:521c017508af4e1d6d9d8a9d90f47a11c61197ad63b2178342489de42540a615", size = 22969303, upload-time = "2025-10-22T14:50:58.768Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl", hash = "sha256:65939ab4c8dcd0c42185c2d0d1a9d60b254dc8c46fc4fdb286b63c51e9358e07", size = 12293385, upload-time = "2025-10-22T14:50:54.075Z" },
+]
+
+[[package]]
+name = "jupyterlab-pygments"
+version = "0.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" },
+]
+
+[[package]]
+name = "jupyterlab-server"
+version = "2.28.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "babel" },
+ { name = "jinja2" },
+ { name = "json5" },
+ { name = "jsonschema" },
+ { name = "jupyter-server" },
+ { name = "packaging" },
+ { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" },
+]
+
+[[package]]
+name = "lark"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
+ { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
+ { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
+ { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
+ { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
+ { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
+ { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
+ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
+]
+
+[[package]]
+name = "matplotlib-inline"
+version = "0.2.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" },
+]
+
+[[package]]
+name = "mistune"
+version = "3.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588, upload-time = "2025-08-29T07:20:43.594Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" },
+]
+
+[[package]]
+name = "narwhals"
+version = "2.10.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c5/dc/8db74daf8c2690ec696c1d772a33cc01511559ee8a9e92d7ed85a18e3c22/narwhals-2.10.2.tar.gz", hash = "sha256:ff738a08bc993cbb792266bec15346c1d85cc68fdfe82a23283c3713f78bd354", size = 584954, upload-time = "2025-11-04T16:36:42.281Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/a9/9e02fa97e421a355fc5e818e9c488080fce04a8e0eebb3ed75a84f041c4a/narwhals-2.10.2-py3-none-any.whl", hash = "sha256:059cd5c6751161b97baedcaf17a514c972af6a70f36a89af17de1a0caf519c43", size = 419573, upload-time = "2025-11-04T16:36:40.574Z" },
+]
+
+[[package]]
+name = "nbclient"
+version = "0.10.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jupyter-client" },
+ { name = "jupyter-core" },
+ { name = "nbformat" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" },
+]
+
+[[package]]
+name = "nbconvert"
+version = "7.16.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "beautifulsoup4" },
+ { name = "bleach", extra = ["css"] },
+ { name = "defusedxml" },
+ { name = "jinja2" },
+ { name = "jupyter-core" },
+ { name = "jupyterlab-pygments" },
+ { name = "markupsafe" },
+ { name = "mistune" },
+ { name = "nbclient" },
+ { name = "nbformat" },
+ { name = "packaging" },
+ { name = "pandocfilters" },
+ { name = "pygments" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" },
+]
+
+[[package]]
+name = "nbformat"
+version = "5.10.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "fastjsonschema" },
+ { name = "jsonschema" },
+ { name = "jupyter-core" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" },
+]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
+]
+
+[[package]]
+name = "notebook"
+version = "7.4.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jupyter-server" },
+ { name = "jupyterlab" },
+ { name = "jupyterlab-server" },
+ { name = "notebook-shim" },
+ { name = "tornado" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/09/f6f64ba156842ef68d3ea763fa171a2f7e7224f200a15dd4af5b83c34756/notebook-7.4.7.tar.gz", hash = "sha256:3f0a04027dfcee8a876de48fba13ab77ec8c12f72f848a222ed7f5081b9e342a", size = 13937702, upload-time = "2025-09-27T08:00:22.536Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6c/d7/06d13087e20388926e7423d2489e728d2e59f2453039cdb0574a7c070e76/notebook-7.4.7-py3-none-any.whl", hash = "sha256:362b7c95527f7dd3c4c84d410b782872fd9c734fb2524c11dd92758527b6eda6", size = 14342894, upload-time = "2025-09-27T08:00:18.496Z" },
+]
+
+[[package]]
+name = "notebook-shim"
+version = "0.2.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jupyter-server" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.3.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" },
+ { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" },
+ { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" },
+ { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" },
+ { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" },
+ { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" },
+ { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" },
+ { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" },
+ { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" },
+ { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" },
+ { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" },
+ { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" },
+ { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" },
+ { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" },
+ { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" },
+ { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" },
+ { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" },
+ { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "pandas"
+version = "2.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+ { name = "python-dateutil" },
+ { name = "pytz" },
+ { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" },
+ { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" },
+ { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" },
+ { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" },
+ { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" },
+ { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" },
+ { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" },
+ { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" },
+ { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" },
+ { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" },
+ { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" },
+ { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" },
+ { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" },
+ { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" },
+]
+
+[[package]]
+name = "pandocfilters"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" },
+]
+
+[[package]]
+name = "parso"
+version = "0.8.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" },
+]
+
+[[package]]
+name = "pexpect"
+version = "4.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "ptyprocess" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
+]
+
+[[package]]
+name = "plotly"
+version = "6.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "narwhals" },
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/e6/b768650072837505804bed4790c5449ba348a3b720e27ca7605414e998cd/plotly-6.4.0.tar.gz", hash = "sha256:68c6db2ed2180289ef978f087841148b7efda687552276da15a6e9b92107052a", size = 7012379, upload-time = "2025-11-04T17:59:26.45Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/ae/89b45ccccfeebc464c9233de5675990f75241b8ee4cd63227800fdf577d1/plotly-6.4.0-py3-none-any.whl", hash = "sha256:a1062eafbdc657976c2eedd276c90e184ccd6c21282a5e9ee8f20efca9c9a4c5", size = 9892458, upload-time = "2025-11-04T17:59:22.622Z" },
+]
+
+[[package]]
+name = "prometheus-client"
+version = "0.23.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" },
+]
+
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.52"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "wcwidth" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
+]
+
+[[package]]
+name = "psutil"
+version = "7.1.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" },
+ { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" },
+ { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" },
+ { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" },
+ { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" },
+ { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" },
+ { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" },
+ { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" },
+]
+
+[[package]]
+name = "ptyprocess"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" },
+]
+
+[[package]]
+name = "pure-eval"
+version = "0.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "2.23"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.12.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.41.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" },
+ { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" },
+ { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" },
+ { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" },
+ { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" },
+ { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" },
+ { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" },
+ { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" },
+ { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" },
+ { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" },
+ { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" },
+ { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" },
+ { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" },
+ { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" },
+ { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" },
+ { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" },
+ { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" },
+ { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "python-json-logger"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" },
+]
+
+[[package]]
+name = "pytz"
+version = "2025.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
+]
+
+[[package]]
+name = "pywinpty"
+version = "3.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386, upload-time = "2025-10-03T21:18:50.477Z" },
+ { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834, upload-time = "2025-10-03T21:19:25.688Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[package]]
+name = "pyzmq"
+version = "27.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "implementation_name == 'pypy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" },
+ { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" },
+ { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" },
+ { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" },
+ { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" },
+ { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" },
+ { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" },
+ { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" },
+ { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" },
+ { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+]
+
+[[package]]
+name = "rfc3339-validator"
+version = "0.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
+]
+
+[[package]]
+name = "rfc3986-validator"
+version = "0.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" },
+]
+
+[[package]]
+name = "rfc3987-syntax"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "lark" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.28.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/48/dc/95f074d43452b3ef5d06276696ece4b3b5d696e7c9ad7173c54b1390cd70/rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea", size = 27419, upload-time = "2025-10-22T22:24:29.327Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d3/03/ce566d92611dfac0085c2f4b048cd53ed7c274a5c05974b882a908d540a2/rpds_py-0.28.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e9e184408a0297086f880556b6168fa927d677716f83d3472ea333b42171ee3b", size = 366235, upload-time = "2025-10-22T22:22:28.397Z" },
+ { url = "https://files.pythonhosted.org/packages/00/34/1c61da1b25592b86fd285bd7bd8422f4c9d748a7373b46126f9ae792a004/rpds_py-0.28.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:edd267266a9b0448f33dc465a97cfc5d467594b600fe28e7fa2f36450e03053a", size = 348241, upload-time = "2025-10-22T22:22:30.171Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/00/ed1e28616848c61c493a067779633ebf4b569eccaacf9ccbdc0e7cba2b9d/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85beb8b3f45e4e32f6802fb6cd6b17f615ef6c6a52f265371fb916fae02814aa", size = 378079, upload-time = "2025-10-22T22:22:31.644Z" },
+ { url = "https://files.pythonhosted.org/packages/11/b2/ccb30333a16a470091b6e50289adb4d3ec656fd9951ba8c5e3aaa0746a67/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2412be8d00a1b895f8ad827cc2116455196e20ed994bb704bf138fe91a42724", size = 393151, upload-time = "2025-10-22T22:22:33.453Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/d0/73e2217c3ee486d555cb84920597480627d8c0240ff3062005c6cc47773e/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf128350d384b777da0e68796afdcebc2e9f63f0e9f242217754e647f6d32491", size = 517520, upload-time = "2025-10-22T22:22:34.949Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/91/23efe81c700427d0841a4ae7ea23e305654381831e6029499fe80be8a071/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2036d09b363aa36695d1cc1a97b36865597f4478470b0697b5ee9403f4fe399", size = 408699, upload-time = "2025-10-22T22:22:36.584Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/ee/a324d3198da151820a326c1f988caaa4f37fc27955148a76fff7a2d787a9/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e1e9be4fa6305a16be628959188e4fd5cd6f1b0e724d63c6d8b2a8adf74ea6", size = 385720, upload-time = "2025-10-22T22:22:38.014Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ad/e68120dc05af8b7cab4a789fccd8cdcf0fe7e6581461038cc5c164cd97d2/rpds_py-0.28.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0a403460c9dd91a7f23fc3188de6d8977f1d9603a351d5db6cf20aaea95b538d", size = 401096, upload-time = "2025-10-22T22:22:39.869Z" },
+ { url = "https://files.pythonhosted.org/packages/99/90/c1e070620042459d60df6356b666bb1f62198a89d68881816a7ed121595a/rpds_py-0.28.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d7366b6553cdc805abcc512b849a519167db8f5e5c3472010cd1228b224265cb", size = 411465, upload-time = "2025-10-22T22:22:41.395Z" },
+ { url = "https://files.pythonhosted.org/packages/68/61/7c195b30d57f1b8d5970f600efee72a4fad79ec829057972e13a0370fd24/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b43c6a3726efd50f18d8120ec0551241c38785b68952d240c45ea553912ac41", size = 558832, upload-time = "2025-10-22T22:22:42.871Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/3d/06f3a718864773f69941d4deccdf18e5e47dd298b4628062f004c10f3b34/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0cb7203c7bc69d7c1585ebb33a2e6074492d2fc21ad28a7b9d40457ac2a51ab7", size = 583230, upload-time = "2025-10-22T22:22:44.877Z" },
+ { url = "https://files.pythonhosted.org/packages/66/df/62fc783781a121e77fee9a21ead0a926f1b652280a33f5956a5e7833ed30/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a52a5169c664dfb495882adc75c304ae1d50df552fbd68e100fdc719dee4ff9", size = 553268, upload-time = "2025-10-22T22:22:46.441Z" },
+ { url = "https://files.pythonhosted.org/packages/84/85/d34366e335140a4837902d3dea89b51f087bd6a63c993ebdff59e93ee61d/rpds_py-0.28.0-cp313-cp313-win32.whl", hash = "sha256:2e42456917b6687215b3e606ab46aa6bca040c77af7df9a08a6dcfe8a4d10ca5", size = 217100, upload-time = "2025-10-22T22:22:48.342Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/1c/f25a3f3752ad7601476e3eff395fe075e0f7813fbb9862bd67c82440e880/rpds_py-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:e0a0311caedc8069d68fc2bf4c9019b58a2d5ce3cd7cb656c845f1615b577e1e", size = 227759, upload-time = "2025-10-22T22:22:50.219Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/d6/5f39b42b99615b5bc2f36ab90423ea404830bdfee1c706820943e9a645eb/rpds_py-0.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:04c1b207ab8b581108801528d59ad80aa83bb170b35b0ddffb29c20e411acdc1", size = 217326, upload-time = "2025-10-22T22:22:51.647Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/8b/0c69b72d1cee20a63db534be0df271effe715ef6c744fdf1ff23bb2b0b1c/rpds_py-0.28.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f296ea3054e11fc58ad42e850e8b75c62d9a93a9f981ad04b2e5ae7d2186ff9c", size = 355736, upload-time = "2025-10-22T22:22:53.211Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/6d/0c2ee773cfb55c31a8514d2cece856dd299170a49babd50dcffb15ddc749/rpds_py-0.28.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5a7306c19b19005ad98468fcefeb7100b19c79fc23a5f24a12e06d91181193fa", size = 342677, upload-time = "2025-10-22T22:22:54.723Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/1c/22513ab25a27ea205144414724743e305e8153e6abe81833b5e678650f5a/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5d9b86aa501fed9862a443c5c3116f6ead8bc9296185f369277c42542bd646b", size = 371847, upload-time = "2025-10-22T22:22:56.295Z" },
+ { url = "https://files.pythonhosted.org/packages/60/07/68e6ccdb4b05115ffe61d31afc94adef1833d3a72f76c9632d4d90d67954/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5bbc701eff140ba0e872691d573b3d5d30059ea26e5785acba9132d10c8c31d", size = 381800, upload-time = "2025-10-22T22:22:57.808Z" },
+ { url = "https://files.pythonhosted.org/packages/73/bf/6d6d15df80781d7f9f368e7c1a00caf764436518c4877fb28b029c4624af/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5690671cd672a45aa8616d7374fdf334a1b9c04a0cac3c854b1136e92374fe", size = 518827, upload-time = "2025-10-22T22:22:59.826Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/d3/2decbb2976cc452cbf12a2b0aaac5f1b9dc5dd9d1f7e2509a3ee00421249/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f1d92ecea4fa12f978a367c32a5375a1982834649cdb96539dcdc12e609ab1a", size = 399471, upload-time = "2025-10-22T22:23:01.968Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/2c/f30892f9e54bd02e5faca3f6a26d6933c51055e67d54818af90abed9748e/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d252db6b1a78d0a3928b6190156042d54c93660ce4d98290d7b16b5296fb7cc", size = 377578, upload-time = "2025-10-22T22:23:03.52Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/5d/3bce97e5534157318f29ac06bf2d279dae2674ec12f7cb9c12739cee64d8/rpds_py-0.28.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d61b355c3275acb825f8777d6c4505f42b5007e357af500939d4a35b19177259", size = 390482, upload-time = "2025-10-22T22:23:05.391Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/f0/886bd515ed457b5bd93b166175edb80a0b21a210c10e993392127f1e3931/rpds_py-0.28.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:acbe5e8b1026c0c580d0321c8aae4b0a1e1676861d48d6e8c6586625055b606a", size = 402447, upload-time = "2025-10-22T22:23:06.93Z" },
+ { url = "https://files.pythonhosted.org/packages/42/b5/71e8777ac55e6af1f4f1c05b47542a1eaa6c33c1cf0d300dca6a1c6e159a/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8aa23b6f0fc59b85b4c7d89ba2965af274346f738e8d9fc2455763602e62fd5f", size = 552385, upload-time = "2025-10-22T22:23:08.557Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/cb/6ca2d70cbda5a8e36605e7788c4aa3bea7c17d71d213465a5a675079b98d/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7b14b0c680286958817c22d76fcbca4800ddacef6f678f3a7c79a1fe7067fe37", size = 575642, upload-time = "2025-10-22T22:23:10.348Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d4/407ad9960ca7856d7b25c96dcbe019270b5ffdd83a561787bc682c797086/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bcf1d210dfee61a6c86551d67ee1031899c0fdbae88b2d44a569995d43797712", size = 544507, upload-time = "2025-10-22T22:23:12.434Z" },
+ { url = "https://files.pythonhosted.org/packages/51/31/2f46fe0efcac23fbf5797c6b6b7e1c76f7d60773e525cb65fcbc582ee0f2/rpds_py-0.28.0-cp313-cp313t-win32.whl", hash = "sha256:3aa4dc0fdab4a7029ac63959a3ccf4ed605fee048ba67ce89ca3168da34a1342", size = 205376, upload-time = "2025-10-22T22:23:13.979Z" },
+ { url = "https://files.pythonhosted.org/packages/92/e4/15947bda33cbedfc134490a41841ab8870a72a867a03d4969d886f6594a2/rpds_py-0.28.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7b7d9d83c942855e4fdcfa75d4f96f6b9e272d42fffcb72cd4bb2577db2e2907", size = 215907, upload-time = "2025-10-22T22:23:15.5Z" },
+ { url = "https://files.pythonhosted.org/packages/08/47/ffe8cd7a6a02833b10623bf765fbb57ce977e9a4318ca0e8cf97e9c3d2b3/rpds_py-0.28.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:dcdcb890b3ada98a03f9f2bb108489cdc7580176cb73b4f2d789e9a1dac1d472", size = 353830, upload-time = "2025-10-22T22:23:17.03Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/9f/890f36cbd83a58491d0d91ae0db1702639edb33fb48eeb356f80ecc6b000/rpds_py-0.28.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f274f56a926ba2dc02976ca5b11c32855cbd5925534e57cfe1fda64e04d1add2", size = 341819, upload-time = "2025-10-22T22:23:18.57Z" },
+ { url = "https://files.pythonhosted.org/packages/09/e3/921eb109f682aa24fb76207698fbbcf9418738f35a40c21652c29053f23d/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe0438ac4a29a520ea94c8c7f1754cdd8feb1bc490dfda1bfd990072363d527", size = 373127, upload-time = "2025-10-22T22:23:20.216Z" },
+ { url = "https://files.pythonhosted.org/packages/23/13/bce4384d9f8f4989f1a9599c71b7a2d877462e5fd7175e1f69b398f729f4/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a358a32dd3ae50e933347889b6af9a1bdf207ba5d1a3f34e1a38cd3540e6733", size = 382767, upload-time = "2025-10-22T22:23:21.787Z" },
+ { url = "https://files.pythonhosted.org/packages/23/e1/579512b2d89a77c64ccef5a0bc46a6ef7f72ae0cf03d4b26dcd52e57ee0a/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e80848a71c78aa328fefaba9c244d588a342c8e03bda518447b624ea64d1ff56", size = 517585, upload-time = "2025-10-22T22:23:23.699Z" },
+ { url = "https://files.pythonhosted.org/packages/62/3c/ca704b8d324a2591b0b0adcfcaadf9c862375b11f2f667ac03c61b4fd0a6/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f586db2e209d54fe177e58e0bc4946bea5fb0102f150b1b2f13de03e1f0976f8", size = 399828, upload-time = "2025-10-22T22:23:25.713Z" },
+ { url = "https://files.pythonhosted.org/packages/da/37/e84283b9e897e3adc46b4c88bb3f6ec92a43bd4d2f7ef5b13459963b2e9c/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ae8ee156d6b586e4292491e885d41483136ab994e719a13458055bec14cf370", size = 375509, upload-time = "2025-10-22T22:23:27.32Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/c2/a980beab869d86258bf76ec42dec778ba98151f253a952b02fe36d72b29c/rpds_py-0.28.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a805e9b3973f7e27f7cab63a6b4f61d90f2e5557cff73b6e97cd5b8540276d3d", size = 392014, upload-time = "2025-10-22T22:23:29.332Z" },
+ { url = "https://files.pythonhosted.org/packages/da/b5/b1d3c5f9d3fa5aeef74265f9c64de3c34a0d6d5cd3c81c8b17d5c8f10ed4/rpds_py-0.28.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d3fd16b6dc89c73a4da0b4ac8b12a7ecc75b2864b95c9e5afed8003cb50a728", size = 402410, upload-time = "2025-10-22T22:23:31.14Z" },
+ { url = "https://files.pythonhosted.org/packages/74/ae/cab05ff08dfcc052afc73dcb38cbc765ffc86f94e966f3924cd17492293c/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6796079e5d24fdaba6d49bda28e2c47347e89834678f2bc2c1b4fc1489c0fb01", size = 553593, upload-time = "2025-10-22T22:23:32.834Z" },
+ { url = "https://files.pythonhosted.org/packages/70/80/50d5706ea2a9bfc9e9c5f401d91879e7c790c619969369800cde202da214/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:76500820c2af232435cbe215e3324c75b950a027134e044423f59f5b9a1ba515", size = 576925, upload-time = "2025-10-22T22:23:34.47Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/12/85a57d7a5855a3b188d024b099fd09c90db55d32a03626d0ed16352413ff/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bbdc5640900a7dbf9dd707fe6388972f5bbd883633eb68b76591044cfe346f7e", size = 542444, upload-time = "2025-10-22T22:23:36.093Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/65/10643fb50179509150eb94d558e8837c57ca8b9adc04bd07b98e57b48f8c/rpds_py-0.28.0-cp314-cp314-win32.whl", hash = "sha256:adc8aa88486857d2b35d75f0640b949759f79dc105f50aa2c27816b2e0dd749f", size = 207968, upload-time = "2025-10-22T22:23:37.638Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/84/0c11fe4d9aaea784ff4652499e365963222481ac647bcd0251c88af646eb/rpds_py-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:66e6fa8e075b58946e76a78e69e1a124a21d9a48a5b4766d15ba5b06869d1fa1", size = 218876, upload-time = "2025-10-22T22:23:39.179Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/e0/3ab3b86ded7bb18478392dc3e835f7b754cd446f62f3fc96f4fe2aca78f6/rpds_py-0.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6fe887c2c5c59413353b7c0caff25d0e566623501ccfff88957fa438a69377d", size = 212506, upload-time = "2025-10-22T22:23:40.755Z" },
+ { url = "https://files.pythonhosted.org/packages/51/ec/d5681bb425226c3501eab50fc30e9d275de20c131869322c8a1729c7b61c/rpds_py-0.28.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7a69df082db13c7070f7b8b1f155fa9e687f1d6aefb7b0e3f7231653b79a067b", size = 355433, upload-time = "2025-10-22T22:23:42.259Z" },
+ { url = "https://files.pythonhosted.org/packages/be/ec/568c5e689e1cfb1ea8b875cffea3649260955f677fdd7ddc6176902d04cd/rpds_py-0.28.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b1cde22f2c30ebb049a9e74c5374994157b9b70a16147d332f89c99c5960737a", size = 342601, upload-time = "2025-10-22T22:23:44.372Z" },
+ { url = "https://files.pythonhosted.org/packages/32/fe/51ada84d1d2a1d9d8f2c902cfddd0133b4a5eb543196ab5161d1c07ed2ad/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5338742f6ba7a51012ea470bd4dc600a8c713c0c72adaa0977a1b1f4327d6592", size = 372039, upload-time = "2025-10-22T22:23:46.025Z" },
+ { url = "https://files.pythonhosted.org/packages/07/c1/60144a2f2620abade1a78e0d91b298ac2d9b91bc08864493fa00451ef06e/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1460ebde1bcf6d496d80b191d854adedcc619f84ff17dc1c6d550f58c9efbba", size = 382407, upload-time = "2025-10-22T22:23:48.098Z" },
+ { url = "https://files.pythonhosted.org/packages/45/ed/091a7bbdcf4038a60a461df50bc4c82a7ed6d5d5e27649aab61771c17585/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3eb248f2feba84c692579257a043a7699e28a77d86c77b032c1d9fbb3f0219c", size = 518172, upload-time = "2025-10-22T22:23:50.16Z" },
+ { url = "https://files.pythonhosted.org/packages/54/dd/02cc90c2fd9c2ef8016fd7813bfacd1c3a1325633ec8f244c47b449fc868/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3bbba5def70b16cd1c1d7255666aad3b290fbf8d0fe7f9f91abafb73611a91", size = 399020, upload-time = "2025-10-22T22:23:51.81Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/81/5d98cc0329bbb911ccecd0b9e19fbf7f3a5de8094b4cda5e71013b2dd77e/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3114f4db69ac5a1f32e7e4d1cbbe7c8f9cf8217f78e6e002cedf2d54c2a548ed", size = 377451, upload-time = "2025-10-22T22:23:53.711Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/07/4d5bcd49e3dfed2d38e2dcb49ab6615f2ceb9f89f5a372c46dbdebb4e028/rpds_py-0.28.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4b0cb8a906b1a0196b863d460c0222fb8ad0f34041568da5620f9799b83ccf0b", size = 390355, upload-time = "2025-10-22T22:23:55.299Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/79/9f14ba9010fee74e4f40bf578735cfcbb91d2e642ffd1abe429bb0b96364/rpds_py-0.28.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf681ac76a60b667106141e11a92a3330890257e6f559ca995fbb5265160b56e", size = 403146, upload-time = "2025-10-22T22:23:56.929Z" },
+ { url = "https://files.pythonhosted.org/packages/39/4c/f08283a82ac141331a83a40652830edd3a4a92c34e07e2bbe00baaea2f5f/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1e8ee6413cfc677ce8898d9cde18cc3a60fc2ba756b0dec5b71eb6eb21c49fa1", size = 552656, upload-time = "2025-10-22T22:23:58.62Z" },
+ { url = "https://files.pythonhosted.org/packages/61/47/d922fc0666f0dd8e40c33990d055f4cc6ecff6f502c2d01569dbed830f9b/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b3072b16904d0b5572a15eb9d31c1954e0d3227a585fc1351aa9878729099d6c", size = 576782, upload-time = "2025-10-22T22:24:00.312Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/0c/5bafdd8ccf6aa9d3bfc630cfece457ff5b581af24f46a9f3590f790e3df2/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b670c30fd87a6aec281c3c9896d3bae4b205fd75d79d06dc87c2503717e46092", size = 544671, upload-time = "2025-10-22T22:24:02.297Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/37/dcc5d8397caa924988693519069d0beea077a866128719351a4ad95e82fc/rpds_py-0.28.0-cp314-cp314t-win32.whl", hash = "sha256:8014045a15b4d2b3476f0a287fcc93d4f823472d7d1308d47884ecac9e612be3", size = 205749, upload-time = "2025-10-22T22:24:03.848Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/69/64d43b21a10d72b45939a28961216baeb721cc2a430f5f7c3bfa21659a53/rpds_py-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7a4e59c90d9c27c561eb3160323634a9ff50b04e4f7820600a2beb0ac90db578", size = 216233, upload-time = "2025-10-22T22:24:05.471Z" },
+]
+
+[[package]]
+name = "send2trash"
+version = "1.8.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" },
+]
+
+[[package]]
+name = "setuptools"
+version = "80.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" },
+]
+
+[[package]]
+name = "stack-data"
+version = "0.6.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "asttokens" },
+ { name = "executing" },
+ { name = "pure-eval" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
+]
+
+[[package]]
+name = "terminado"
+version = "0.18.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "ptyprocess", marker = "os_name != 'nt'" },
+ { name = "pywinpty", marker = "os_name == 'nt'" },
+ { name = "tornado" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" },
+]
+
+[[package]]
+name = "tinycss2"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" },
+]
+
+[[package]]
+name = "tornado"
+version = "6.5.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" },
+ { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" },
+ { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" },
+]
+
+[[package]]
+name = "traitlets"
+version = "5.14.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2025.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
+]
+
+[[package]]
+name = "uri-template"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
+]
+
+[[package]]
+name = "wcwidth"
+version = "0.2.14"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" },
+]
+
+[[package]]
+name = "webcolors"
+version = "25.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" },
+]
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
+]
+
+[[package]]
+name = "websocket-client"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" },
+]
diff --git a/x/gov/testutil/expected_keepers_mocks.go b/x/gov/testutil/expected_keepers_mocks.go
index 3c5b098bb417..d04379a92ef9 100644
--- a/x/gov/testutil/expected_keepers_mocks.go
+++ b/x/gov/testutil/expected_keepers_mocks.go
@@ -16,12 +16,13 @@ import (
address "cosmossdk.io/core/address"
math "cosmossdk.io/math"
types "cosmossdk.io/store/types"
+ gomock "go.uber.org/mock/gomock"
+
types0 "github.com/cosmos/cosmos-sdk/types"
query "github.com/cosmos/cosmos-sdk/types/query"
keeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
types1 "github.com/cosmos/cosmos-sdk/x/bank/types"
types2 "github.com/cosmos/cosmos-sdk/x/staking/types"
- gomock "go.uber.org/mock/gomock"
)
// MockAccountKeeper is a mock of AccountKeeper interface.
diff --git a/x/simulation/client/cli/flags.go b/x/simulation/client/cli/flags.go
index 8479b83f5e1c..c8552fec91bc 100644
--- a/x/simulation/client/cli/flags.go
+++ b/x/simulation/client/cli/flags.go
@@ -20,6 +20,7 @@ var (
FlagSeedValue int64
FlagInitialBlockHeightValue int
FlagNumBlocksValue int
+ FlagNumRunsValue int
FlagBlockSizeValue int
FlagLeanValue bool
FlagCommitValue bool
@@ -51,6 +52,7 @@ func GetSimulatorFlags() {
flag.StringVar(&FlagExportStatsPathValue, "ExportStatsPath", "", "custom file path to save the exported simulation statistics JSON")
flag.Int64Var(&FlagSeedValue, "Seed", DefaultSeedValue, "simulation random seed")
flag.IntVar(&FlagInitialBlockHeightValue, "InitialBlockHeight", 1, "initial block to start the simulation")
+ flag.IntVar(&FlagNumRunsValue, "NumRuns", 3, "number of runs to run for simulations that do multiple runs at once")
flag.IntVar(&FlagNumBlocksValue, "NumBlocks", 500, "number of new blocks to simulate from the initial block height")
flag.IntVar(&FlagBlockSizeValue, "BlockSize", 200, "operations per block")
flag.BoolVar(&FlagLeanValue, "Lean", false, "lean simulation log output")
@@ -81,6 +83,7 @@ func NewConfigFromFlags() simulation.Config {
Seed: FlagSeedValue,
InitialBlockHeight: FlagInitialBlockHeightValue,
GenesisTime: FlagGenesisTimeValue,
+ NumRuns: FlagNumRunsValue,
NumBlocks: FlagNumBlocksValue,
BlockSize: FlagBlockSizeValue,
Lean: FlagLeanValue,