diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 000000000..4f45119ab
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,43 @@
+name: build-test
+
+on:
+ push:
+ branches:
+ - "main"
+ pull_request:
+ branches:
+ - "main"
+ - "develop"
+
+jobs:
+ audit:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.6.0
+ with:
+ access_token: ${{ secrets.GITHUB_TOKEN }}
+ - uses: actions/checkout@v2
+
+ - name: Set up Go
+ uses: actions/setup-go@v2
+ with:
+ go-version: 1.24.0
+
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Verify dependencies
+ run: go mod verify
+
+ - name: Build all
+ run: make all
+
+ - name: Run tests
+ run: go test -short ./...
+
+ - name: Upload coverage to Codecov
+ run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}
+
+ - name: Clean
+ run: make clean
diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
new file mode 100644
index 000000000..c0f5ed78e
--- /dev/null
+++ b/.github/workflows/docker-image.yml
@@ -0,0 +1,46 @@
+name: docker-image-ci
+
+on:
+ push:
+ branches:
+ - "main"
+ pull_request:
+ branches:
+ - "main"
+ - "develop"
+
+jobs:
+ push-image:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.6.0
+ with:
+ access_token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Login to GitHub Container Registry
+ if: ${{ !env.ACT }}
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ secrets.CR_USER }}
+ password: ${{ secrets.CR_PAT }}
+
+ - uses: actions/checkout@v2
+ - name: Build & Publish the Docker image
+ if: ${{ !env.ACT }}
+ run: |
+ docker build . --file Dockerfile --tag ghcr.io/covalenthq/bsp-geth:latest
+ docker push ghcr.io/covalenthq/bsp-geth:latest
+
+ # - name: Start containers
+ # run: docker-compose -f "docker-compose.yml" up --build --remove-orphans --force-recreate --exit-code-from agent
+
+ # - name: Check running bsp-geth
+ # run: docker inspect bsp-geth
+
+ # - name: Check running containers
+ # run: docker ps
+
+ # - name: Stop containers
+ # if: always()
+ # run: docker-compose -f "docker-compose.yml" down
diff --git a/.github/workflows/gcr-image.yml b/.github/workflows/gcr-image.yml
new file mode 100644
index 000000000..695fd2374
--- /dev/null
+++ b/.github/workflows/gcr-image.yml
@@ -0,0 +1,31 @@
+name: gcr-image
+
+on:
+ push:
+ branches:
+ - "main"
+ pull_request:
+ branches:
+ - "main"
+ - "develop"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.6.0
+ with:
+ access_token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Login to GCR
+ uses: docker/login-action@v2
+ with:
+ registry: us-docker.pkg.dev
+ username: _json_key
+ password: ${{ secrets.GCR_JSON_KEY }}
+
+ - uses: actions/checkout@v2
+ # - uses: satackey/action-docker-layer-caching@v0.0.11
+ - name: Build & Publish the Docker image
+ run: |
+ docker buildx create --name builder --use --platform=linux/amd64 && docker buildx build --platform=linux/amd64 . -t us-docker.pkg.dev/covalent-project/network/bsp-geth:latest --push
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 78410aab1..6e6e3a97a 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -2,9 +2,9 @@ name: i386 linux tests
on:
push:
- branches: [ master ]
+ branches: [master]
pull_request:
- branches: [ master ]
+ branches: [master]
workflow_dispatch:
jobs:
@@ -12,32 +12,32 @@ jobs:
name: Lint
runs-on: self-hosted
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v4
- # Cache build tools to avoid downloading them each time
- - uses: actions/cache@v4
- with:
- path: build/cache
- key: ${{ runner.os }}-build-tools-cache-${{ hashFiles('build/checksums.txt') }}
+ # Cache build tools to avoid downloading them each time
+ - uses: actions/cache@v4
+ with:
+ path: build/cache
+ key: ${{ runner.os }}-build-tools-cache-${{ hashFiles('build/checksums.txt') }}
- - name: Set up Go
- uses: actions/setup-go@v5
- with:
- go-version: 1.23.0
- cache: false
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: 1.24.0
+ cache: false
- - name: Run linters
- run: |
- go run build/ci.go lint
- go run build/ci.go check_generate
- go run build/ci.go check_baddeps
+ - name: Run linters
+ run: |
+ go run build/ci.go lint
+ go run build/ci.go check_generate
+ go run build/ci.go check_baddeps
build:
runs-on: self-hosted
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v2
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@v2
with:
go-version: 1.24.0
cache: false
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
new file mode 100644
index 000000000..51d3310af
--- /dev/null
+++ b/.github/workflows/golangci-lint.yml
@@ -0,0 +1,50 @@
+name: golangci-lint
+
+on:
+ push:
+ branches:
+ - "main"
+ pull_request:
+ branches:
+ - "main"
+ - "develop"
+permissions:
+ contents: read
+ # Optional: allow read access to pull request. Use with `only-new-issues` option.
+ # pull-requests: read
+jobs:
+ golangci-build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-go@v3
+ - name: Set up Go 1.24.0
+ uses: actions/setup-go@v3
+ with:
+ go-version: 1.24.0
+ id: go
+ - run: go version
+
+ - name: Lint
+ run: |
+ # curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.51.2
+ # ./bin/golangci-lint run
+
+ # Optional: working directory, useful for monorepos
+ # working-directory: somedir
+
+ # Optional: golangci-lint command line arguments.
+ # args: --issues-exit-code=0
+
+ # Optional: show only new issues if it's a pull request. The default value is `false`.
+ # only-new-issues: true
+
+ # Optional: if set to true then the action will use pre-installed Go.
+ # skip-go-installation: true
+
+ # Optional: if set to true then the action don't cache or restore ~/go/pkg.
+ # skip-pkg-cache: true
+
+ # Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
+ # skip-build-cache: true
diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml
new file mode 100644
index 000000000..a120b7792
--- /dev/null
+++ b/.github/workflows/hadolint.yml
@@ -0,0 +1,21 @@
+name: dockerfile-lint
+
+on:
+ push:
+ branches:
+ - "main"
+ pull_request:
+ branches:
+ - "main"
+ - "develop"
+
+jobs:
+ linter:
+ name: lint-dockerfile
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: lint dockerfile
+ uses: brpaz/hadolint-action@master
+ with:
+ dockerfile: "Dockerfile"
diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml
new file mode 100644
index 000000000..a39c07961
--- /dev/null
+++ b/.github/workflows/tag-release.yaml
@@ -0,0 +1,39 @@
+name: tag-release
+
+on:
+ push:
+ tags:
+ - "v*.*.*"
+
+jobs:
+ tagged-release:
+ name: Tagged Release
+ runs-on: "ubuntu-latest"
+
+ steps:
+ - name: Login to GCR
+ uses: docker/login-action@v2
+ with:
+ registry: us-docker.pkg.dev
+ username: _json_key
+ password: ${{ secrets.GCR_JSON_KEY }}
+
+ - uses: actions/checkout@v2
+
+ # - uses: satackey/action-docker-layer-caching@v0.0.11
+
+ - name: Set env
+ run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
+
+ - name: Build & Publish the Docker image
+ run: |
+ docker buildx create --name builder --use --platform=linux/amd64 && docker buildx build --platform=linux/amd64 . -t us-docker.pkg.dev/covalent-project/network/bsp-geth:stable -t us-docker.pkg.dev/covalent-project/network/bsp-geth:"${{ env.TAG }}" --push
+
+ - uses: "marvinpinto/action-automatic-releases@latest"
+ with:
+ repo_token: "${{ secrets.GITHUB_TOKEN }}"
+ draft: false
+ prerelease: false
+ files: |
+ *.zip
+ *.tar.gz
diff --git a/.gitignore b/.gitignore
index 269455db7..bcf5e7b9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,23 @@ profile.cov
# VS Code
.vscode
+# dashboard
+/dashboard/assets/flow-typed
+/dashboard/assets/node_modules
+/dashboard/assets/stats.json
+/dashboard/assets/bundle.js
+/dashboard/assets/bundle.js.map
+/dashboard/assets/package-lock.json
+
+**/yarn-error.log
+
+data/
+ganache_data/
+.env
+scripts/
+logs/
+coverage.out
+coverage.txt
tests/spec-tests/
# binaries
@@ -55,4 +72,6 @@ cmd/ethkey/ethkey
cmd/evm/evm
cmd/geth/geth
cmd/rlpdump/rlpdump
-cmd/workload/workload
\ No newline at end of file
+cmd/workload/workload
+bin/
+out/
diff --git a/.hadolint.yaml b/.hadolint.yaml
new file mode 100644
index 000000000..3cc66d39f
--- /dev/null
+++ b/.hadolint.yaml
@@ -0,0 +1,4 @@
+ignored:
+ - DL3018
+ - DL3016
+ - DL3059
diff --git a/Dockerfile b/Dockerfile
index 9b70e9e8a..e9db78962 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,31 +1,31 @@
-# Support setting various labels on the final image
-ARG COMMIT=""
-ARG VERSION=""
-ARG BUILDNUM=""
+# # Support setting various labels on the final image
+# ARG COMMIT=""
+# ARG VERSION=""
+# ARG BUILDNUM=""
+ARG USER=$USER
+
# Build Geth in a stock Go builder container
FROM golang:1.24-alpine AS builder
-RUN apk add --no-cache gcc musl-dev linux-headers git
+RUN apk add --no-cache gcc musl-dev linux-headers git make
-# Get dependencies - will also be cached if we won't change go.mod/go.sum
-COPY go.mod /go-ethereum/
-COPY go.sum /go-ethereum/
-RUN cd /go-ethereum && go mod download
-
-ADD . /go-ethereum
-RUN cd /go-ethereum && go run build/ci.go install -static ./cmd/geth
+COPY . /go-ethereum
+WORKDIR /go-ethereum
+RUN go run build/ci.go install -static ./cmd/geth
# Pull Geth into a second stage deploy alpine container
-FROM alpine:latest
+FROM alpine:3.21
RUN apk add --no-cache ca-certificates
+
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
EXPOSE 8545 8546 30303 30303/udp
-ENTRYPOINT ["geth"]
-# Add some metadata labels to help programmatic image consumption
+ENTRYPOINT ["geth", "--mainnet", "--syncmode", "full", "--datadir", "/root/.ethereum/covalent", "--replication.targets", "redis://localhost:6379/?topic=replication", "--replica.result", "true", "--replica.specimen", "true", "--replica.blob", "true"]
+
+# Add some metadata labels to help programatic image consumption
ARG COMMIT=""
ARG VERSION=""
ARG BUILDNUM=""
diff --git a/README.md b/README.md
index 9ccfe933a..0f3bca175 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,366 @@
-## Go Ethereum
+
+
+
+
+
+# Block Specimen Producer (BSP Geth)
+
+* [Introduction](#bsp_intro)
+ * [Resources](#bsp_resources)
+* [Raison d'être](#bsp_why)
+* [Architecture](#bsp_arch)
+* [Docker Run](#docker)
+* [Build & Run](#build_run)
+ * [Flag Definitions](#flag_definitions)
+* [Contributing](./docs/CONTRIBUTING.md)
+* [Go Ethereum](#geth)
+
+## Introduction
+
+Essential to the Covalent Network is the Block Specimen and the Block Specimen Producer (BSP), a bulk export method that ultimately leads to the generation of a canonical representation of a blockchain's historical state. Currently implemented on existing blockchain clients running Geth. It functions currently as an -
+
+1. Blockchain data extractor
+1. Blockchain data normalizer
+
+What is ultimately created is a ‘Block Specimen’, a universal canonical representation of a blockchain's historical state.
+
+There are two further considerations regarding the Block Specimen.
+
+1. The BSP is completely standalone on forks of Geth.
+1. The separation of data storage layer from the block execution and distributed consensus functionality leads to better segregation and upgrades of functionality in the blockchain data processing pipeline.
+
+As a result, anyone can run full tracing on the block specimen and accurately recreate the blockchain without access to a blockchain client software.
+
+## Resources
+
+Production of Block Specimens forms the core of the network’s data objects specification. These objects are created with the aid of three main pieces of open-source software provided by Covalent for the network’s decentralized stack.
+
+1. [Block Specimen Producer (BSP Geth)](https://github.com/covalenthq/bsp-geth) - Operator run & deployed
+
+1. [BSP Agent](https://github.com/covalenthq/bsp-agent) - Operator run & deployed
+
+1. [BSP Proof-chain](https://github.com/covalenthq/bsp-staking) - Covalent operated & pre-deployed
+
+Please refer to these [instructions](https://docs.google.com/document/d/1N_HxUi6ZEkub9EHANe49vkL9iQztVA_ACyfHcOZV5y0/edit?usp=sharing) for running the BSP with the bsp-agent (BSP Agent).
+
+Please refer to this [whitepaper](https://www.covalenthq.com/static/documents/Block%20Specimen%20Whitepaper%20V1.1.pdf) to understand more about its function.
+
+
+## Raison d'être
+
+
+The blockchain space has been and will continue to be laser-focused on *write* scalability. That is, actually writing on the blockchain (confirming a transaction) and doing so efficiently. And the projects tackling this issue, be it Layer 1s or Layer 2s, have certainly made operating in this space more accommodating, leading to increased adoption as powerful and scalable applications are being developed.
+
+However, this is only one side of the scalability issue that troubles the space. On the flip side you have the issue of *read scalability*. This is different to *write* as the focus with *read* is on extracting and reading the data on the blockchain, whether that be Ethereum, Avalanche or Solana.
+
+One common method of reading data from Ethereum for example is the JSON RPC Layer. A number of issues present themselves however when doing so.
+
+- **Slow**: One needs to make a series of individual data queries to extract the block and its constituent elements like transactions and receipts.
+
+- **Not Multiversion**: Multiversion concurrency control methods are traditionally employed in databases to ensure point-in-time consistent views if multiple parties are viewing or querying the database. Such methods do not exist in web3.
+
+- **Expensive**: To access historical data at any point in time, you need to run your blockchain clients in a mode known as “full archive nodes” - which requires specialized and expensive hardware to scale.
+
+- **The Purge:** For Ethereum specifically, Vitalik recently outlined an updated roadmap for its development which included a phase titled ‘The Purge’. Once this phase is implemented, clients will no longer store historical data older than a year. Hence, alternatives will be needed to access Ethereum's full historical state.
+
+Meanwhile, data mappers and static dashboards are great for examining specific metrics and small tables (so long as the smart contract is decoded) but lack flexibility and scalability. Our belief is that -
+
+1. fast, cheap and accessible read capabilities will lead to more diverse and better-adapted blockchain technologies.
+
+1. should be accessible to all, no matter the skill level.
+
+The Block Specimen is **the solution** to tackle the read scalability issues that currently plague blockchains.
+
+## Architecture
+
+
+
+While Block Specimens are currently being created internally at Covalent for each respective blockchain indexed, the Covalent Network shifts this responsibility to operators (anyone performing a role on the Covalent Network). Any operator on the network will be able to opt in to act as a Block Specimen Producer (BSP).
+
+To ensure that the data within the block-specimens that operators create is reliable and honest, a production proof is created for every Block Specimen produced. These will be published to proofing contract deployed by Covalent. Therefore, Block Specimen proofs can be compared and any deviations in the data either accidentally or malicious will have mismatching proofs.
+
+
+
+
+In sum, it is the responsibility of the BSPs to consume blocks from external blockchains and publish both the BSP along with a production proof to the Covalent virtual chain. As the network is developed, Covalent will be shifting the responsibility of running the Block Specimen software to operators (anyone performing a role on the Covalent Network). These Block Specimens will consume blocks from external blockchains and feed the entire network with the data needed to answer user queries.
+
+How does this create and accrue value for the Covalent Network? As Block Specimen Producers publish more data to Covalent Network, developers will be attracted. With a growing developer base on the Covalent Network, there will be a greater appetite for blockchain data. And hence, the cycle begins again, with Block Specimens expected to meet this demand for data.
+Of course, operators who successfully perform this role will be compensated in CQT.
+
+## Docker Run
+
+Please install [docker and docker-compose](https://docs.docker.com/compose/install/).
+
+Employ `docker-compose` to get all the necessary services along with BSP Geth to also get running alongside it. With `docker-compose` BSP Geth creates block-specimens that are extracted Live for Ethereum Mainnet, pushes them to the Redis stream queue, the BSP Agent service then reads the RLP block-specimens and processes the messages from the redis stream and stores/uploads them according to the configuration given to it. The BSP Agent makes statements about the block-specimens by writing to a proxy proof-chain smart contract that is deployed on a test ganache blockchain i.e the proving aspects happen locally and not on a public blockchain. The Specimens stored locally or uploaded to google cloud storage however retain full validity (if the BSP Geth / Agent source code is not altered.) Add an .env file (if needed) to accommodate the env vars.
+
+In the future, for the BSP Agent the flags `--eth-client` pointing to a Mainnet ethereum client & `--proof-chain-address` pointing to the correct version of the deployed proofing contract along with the an `.env` file with `ETH_PRIVATE_KEY` var will have to be updated to run entirely for CQT Mainnet.
+These changes can also be directly adapted into the docker-compose.yml file in this directory.
+
+The list of all services are -
+
+1. redis-srv (Open source (BSD licensed), in-memory data structure store)
+1. redis-commander-web (Redis web management tool written in node.js)
+1. ganache-cli (Ethereum blockchain & client)
+1. proof-chain (Validation (proofing) smart-contracts)
+1. bsp-agent (Block specimen decoder, packer, prover and storer)
+
+```bash
+git clone git@github.com:covalenthq/bsp-geth.git
+cd bsp-geth
+docker-compose -f "docker-compose-testnet.yml" up
+```
+
+If all the services are up and running well, expect to see the logs similar to the following, in approx ~ 10 mins, as the node begins to sync and export Block Specimens. Please note we don't advise running bsp-geth and bsp-agent in production with docker (this is only for demo purposes).
+
+```bash
+bsp-geth | INFO [02-04|18:59:33.731|core/block_replica.go:36] Creating block replication event block number=139 hash=0x41d2931a4495deabbf9f58181a48d29c89036c8fb8b9ecedb5f23805cc6f5e34
+bsp-geth | INFO [02-04|18:59:33.745|core/block_replica.go:36] Creating block replication event block number=140 hash=0xe2c1e8200ef2e9fba09979f0b504dc52c068719623c7064904c7bd3e9365acc1
+ganache-cli |
+ganache-cli | Transaction: 0x398c2d9c820a6bbdfd7de696b7e049b25114285f00a4c12f49ef492bf2522858
+ganache-cli | Gas usage: 48457
+ganache-cli | Block Number: 9
+ganache-cli | Block Time: Fri Feb 04 2022 18:59:33 GMT+0000 (Coordinated Universal Time)
+ganache-cli |
+ganache-cli | eth_getTransactionReceipt
+bsp-geth | INFO [02-04|18:59:33.819|core/block_replica.go:36] Creating block replication event block number=141 hash=0xeafbe76fdcadc1b69ba248589eb2a674b60b00c84374c149c9deaf5596183932
+bsp-agent | time="2022-02-04T18:59:33Z" level=info msg="Proof-chain tx hash: 0x398c2d9c820a6bbdfd7de696b7e049b25114285f00a4c12f49ef492bf2522858 for block-replica segment: 1-1-10-replica-segment" function=EncodeProveAndUploadReplicaSegment line=63
+bsp-agent | time="2022-02-04T18:59:33Z" level=info msg="File written successfully to: ./bin/block-ethereum/1-1-10-replica-segment-0x398c2d9c820a6bbdfd7de696b7e049b25114285f00a4c12f49ef492bf2522858" function=writeToBinFile line=88
+bsp-geth | INFO [02-04|18:59:33.855|core/block_replica.go:36] Creating block replication event block number=142 hash=0x8ff76dc49f9a1492813a281a474f102890cdd5a42399241d5fa403f201a4d7cf
+bsp-agent |
+bsp-agent | ---> Processing 1-11-20-replica-segment <---
+bsp-agent | time="2022-02-04T18:59:33Z" level=info msg="Submitting block-replica segment proof for: 1-11-20-replica-seg
+```
+
+with occasional responses from `bsp-agent` service such as -
+
+```bash
+bsp-agent | ---> Processing 1-61-70-replica-segment <---
+bsp-agent | time="2022-02-04T19:00:04Z" level=info msg="Submitting block-replica segment proof for: 1-61-70-replica-segment" function=EncodeProveAndUploadReplicaSegment line=57
+bsp-agent | time="2022-02-04T19:00:04Z" level=info msg="Proof-chain tx hash: 0x85b5e7cfa946f3b44b811dce48715841f40627dffb11ebcecb77e3e4a8ef3711 for block-replica segment: 1-61-70-replica-segment" function=EncodeProveAndUploadReplicaSegment line=63
+bsp-agent | time="2022-02-04T19:00:04Z" level=info msg="File written successfully to: ./bin/block-ethereum/1-61-70-replica-segment-0x85b5e7cfa946f3b44b811dce48715841f40627dffb11ebcecb77e3e4a8ef3711" function=writeToBinFile line=88
+```
+
+To inspect the actual block specimen binary files produced, exec into the `bsp-agent` container and run the `bsp-extractor` as shown below -
+
+
+
+```bash
+docker ps
+docker exec -it /bin/bash
+./bsp-extractor --binary-file-path "./bin/block-ethereum/" --codec-path "./codec/block-ethereum.avsc" --indent-json 0
+```
+
+The docker image for this service can be found [here](https://github.com/covalenthq/bsp-geth/pkgs/container/go-ethereum-bsp)
+
+Run only go-ethereum-bsp with the following, though this will not work if the other services in the docker-compose.yml file aren't also initialized.
+
+```bash
+docker pull ghcr.io/covalenthq/bsp-geth-bsp:latest
+docker run ghcr.io/covalenthq/bsp-geth-bsp:latest
+```
+
+## Build & Run
+
+Clone the `covalenthq/bsp-geth` repo and checkout the branch that contains the block specimen patch aka `covalent`
+
+```sh
+git clone git@github.com:covalenthq/bsp-geth.git
+cd bsp-geth
+git checkout main
+```
+
+Build `geth` from source (install [`Go`](https://go.dev/doc/install) if you don’t have it) and other geth developer tools from root. Make sure you also have [`make`](https://www.gnu.org/software/make/) that is used too build and install bsp-geth. If you need all the go-ethereum development related tools do a `make all`.
+
+```bash
+make geth
+```
+
+Start redis (our streaming service) with the following.
+
+```bash
+$ redis-server
+[28550] 01 Aug 19:29:28 # Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf'
+[28550] 01 Aug 19:29:28 * Server started, Redis version 2.2.12
+[28550] 01 Aug 19:29:28 * The server is now ready to accept connections on port 6379
+```
+
+Start redis-cli in a separate terminal so you can see the encoded bsps as they are fed into redis streams.
+
+```bash
+$ redis-cli
+127.0.0.1:6379> ping
+PONG
+```
+
+We are now ready to start accepting stream message into redis locally. Kindly note that [redis streams](https://redis.io/docs/manual/data-types/streams/) uses in-memory data structures to store new messages (but those are written to disk at the time of exit / closing the server) and can lead to large memory requirements if not managed properly.
+
+Now start `geth` from root with the given configuration, here we specify the replication targets (block specimen targets) with redis stream topic key `replication-1`, running `geth` in `full` or `snap` syncmode, exposing the http port for the geth apis are optional. bsp-geth can be run as usually run when run as a full / snap node with the extra flags provided here.
+
+Prior to executing, please replace `` with correct local username within the `--datadir` flag. Everything else remains the same as given below.
+
+```bash
+./build/bin/geth \
+ --mainnet \
+ --log.debug \
+ --syncmode snap \
+ --datadir /scratch/node/ethereum/ \
+ --replication.targets "redis://localhost:6379/?topic=replication-1" \
+ --replica.result \
+ --replica.specimen \
+ --log.file "./logs/geth.log"
+```
+
+`bsp-geth` only produces block specimens for live blocks once state sync is complete. In order to check the status of your sync progress. Connect to the node’s IPC instance to check how far the node is synced.
+
+```bash
+./build/bin/geth attach /scratch/node/ethereum/geth.ipc
+```
+
+Once connected wait for the node at a given `currentBlock` to reach the `highestblock` to start creating live block specimens. `startingBlock` can be 0 or any number depending on the chaindata sync status in `datadir`.
+
+```bash
+Welcome to the Geth JavaScript console!
+
+instance: Geth/v1.10.17-stable-d1a92cb2/darwin-arm64/go1.17.2
+at block: 10487792 (Mon Apr 11 2022 14:01:59 GMT-0700 (PDT))
+ datadir: /scratch/node/ethereum/
+ modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
+
+To exit, press ctrl-d or type exit
+> eth.syncing
+{
+ currentBlock: 10487906,
+ healedBytecodeBytes: 0,
+ healedBytecodes: 0,
+ healedTrienodeBytes: 0,
+ healedTrienodes: 0,
+ healingBytecode: 0,
+ healingTrienodes: 0,
+ highestBlock: 10499433,
+ startingBlock: 10486736,
+ syncedAccountBytes: 0,
+ syncedAccounts: 0,
+ syncedBytecodeBytes: 0,
+ syncedBytecodes: 0,
+ syncedStorage: 0,
+ syncedStorageBytes: 0
+}
+
+> eth.syncing
+false
+```
+
+This can take a few days or a few hours depending on if the source chaindata is already available at the `datadir` location or live sync is being attempted from scratch for a new copy of blockchain data obtained from syncing with peers. In the case of the latter the strength of the network and other factors that affect the Ethereum network devp2p protocol performance can further cause delays.
+
+Once blockchain data state sync is complete and `eth.syncing` returns false. You can expect to see block-specimens in the redis stream. The following logs are captured from `bsp-geth` service as the node begins to export live Block Specimens.
+
+```bash
+INFO [04-11|16:35:48.554|core/chain_replication.go:317] Replication progress sessID=1 queued=1 sent=10960 last=0xffc46213ccd3c55b75f73a0bc29c25780eb37f04c9f2b88179e9d0fb889a4151
+INFO [04-11|16:36:04.183|core/blockchain_insert.go:75] Imported new chain segment blocks=1 txs=63 mgas=13.147 elapsed=252.747ms mgasps=52.015 number=10,486,732 hash=8b57c8..bd5c79 dirty=255.49MiB
+INFO [04-11|16:36:04.189|core/block_replica.go:41] Creating Block Specimen Exported block=10,486,732 hash=0x8b57c8606d74972c59c56f7fe672a30ed6546fc8169b6a2504abb633aebd5c79
+INFO [04-11|16:36:04.189|core/rawdb/chain_iterator.go:338] Unindexed transactions blocks=1 txs=9 tail=8,136,733 elapsed="369.12µs"
+```
+
+The last two lines above show that new block replicas containing the block specimens are being produced and streamed to the redis topic “replication”. After this you can check that redis is stacking up the bsp messages through the redis-cli with the command below (this should give you the number of messages from the stream)
+
+```bash
+$ redis-cli
+127.0.0.1:6379> xlen replication-1
+11696
+```
+
+If it doesn’t - the BSP - producer isn't producing messages! In this case please look at the logs above and see if you have any WARN / DEBUG logs that can be responsible for the inoperation. For quick development iteration and faster network sync - enable a new node key to quickly re-sync with the ethereum network for development and testing by going to the root of go-ethereum and running the bootnode helper.
+
+NOTE: To use the bootnode binary execute `make all` in place of `make geth`, this creates all the additional helper binaries that `bsp-geth` ships with.
+
+```bash
+./build/bin/bootnode -genkey ~/.ethereum/bsp/geth/nodekey
+```
+
+Further, also have [`bsp-agent`](https://github.com/covalenthq/bsp-agent) running alongside consuming messages from redis (this will consume the messages and remove them from the stream key). You should see the occasional responses from `bsp-agent` service such as -
+
+```bash
+time="2022-04-18T17:26:47Z" level=info msg="Initializing Consumer: fb78bb1c-1e14-4905-bb1f-0ea96de8d8b5 | Redis Stream: replication-1 | Consumer Group: replicate-1" function=main line=167
+time="2022-04-18T17:26:47Z" level=info msg="block-specimen not created for: 10430548, base block number divisor is :3" function=processStream line=332
+time="2022-04-18T17:26:47Z" level=info msg="stream ids acked and trimmed: [1648848491276-0], for stream key: replication-1, with current length: 11700" function=processStream line=339
+time="2022-04-18T17:26:47Z" level=info msg="block-specimen not created for: 10430549, base block number divisor is :3" function=processStream line=332
+time="2022-04-18T17:26:47Z" level=info msg="stream ids acked and trimmed: [1648848505274-0], for stream key: replication-1, with current length: 11699" function=processStream line=339
+
+---> Processing 4-10430550-replica <---
+time="2022-04-18T17:26:47Z" level=info msg="Submitting block-replica segment proof for: 4-10430550-replica" function=EncodeProveAndUploadReplicaSegment line=59
+time="2022-04-18T17:26:47Z" level=info msg="binary file should be available: ipfs://QmUQ4XYJv9syrokUfUbhvA4bV8ce7w1Q2dF6NoNDfSDqxc" function=EncodeProveAndUploadReplicaSegment line=80
+time="2022-04-18T17:27:04Z" level=info msg="Proof-chain tx hash: 0xcc8c487a5db0fec423de62f7ac4ca81c630544aa67c432131cabfa35d9703f37 for block-replica segment: 4-10430550-replica" function=EncodeProveAndUploadReplicaSegment line=86
+time="2022-04-18T17:27:04Z" level=info msg="File written successfully to: /scratch/node/block-ethereum/4-10430550-replica-0xcc8c487a5db0fec423de62f7ac4ca81c630544aa67c432131cabfa35d9703f37" function=writeToBinFile line=188
+time="2022-04-18T17:27:04Z" level=info msg="car file location: /tmp/28077399.car\n" function=generateCarFile line=133
+time="2022-04-18T17:27:08Z" level=info msg="File /tmp/28077399.car successfully uploaded to IPFS with pin: QmUQ4XYJv9syrokUfUbhvA4bV8ce7w1Q2dF6NoNDfSDqxc" function=HandleObjectUploadToIPFS line=102
+time="2022-04-18T17:27:08Z" level=info msg="stream ids acked and trimmed: [1648848521276-0], for stream key: replication-1, with current length: 11698" function=processStream line=323
+```
+
+If you see all of the above you're successfully running the full BSP pipeline.
+
+### Flag definitions
+
+`--mainnet` - lets geth know which network to synchronize with, and pull block specimen from, this can be `--ropsten`, `--goerli` , `--mainnet` etc
+
+`--port 0` - will auto-assign a port for geth to talk to other nodes in the network, but this may not work if you are behind a firewall. It would be better to explicitly assign a port and to ensure that port is open to any firewalls.
+
+`--http` - enables the json-rpc api over http
+
+`--log.debug` - enables a detailed log of the processes geth deals with going back and forth between
+
+`--syncmode` - this flag is used to enable different syncing strategies for geth and `full` and `snap` modes are supported for live block-specimen creation
+
+`--datadir` - specifies a local datadir path for geth (note we use “BSP” as the directory name with the Ethereum directory), this can be an pre-exisiting directory with chaindata for the given network flag synced to the most recent block.
+
+`--replication.targets` - this flag lets the BSP know where and how to send the BSP messages (this flag will not function without the usage of either one or both of the flags below, if both are selected a full block-replica is exported
+
+`--replica.result` - this flag lets the BSP know if all fields related to the block-result specification need to be exported (if only this flag is selected the exported object is a block-result)
+
+`--replica.specimen` - this flag lets the BSP know if all fields related to the block-specimen specification need to be exported (if only this flag is selected the exported object is a block-specimen)
+
+If both `--replica-result` & `--replica-specimen` are selected then a `block-replica` is exported containing all the fields for exporting any block fully along with its stored state.
+
+`--log.file` - specifies the file location where the log files have to be placed. In case of error (like permission errors), the logs are not recorded in files.
+
+## Go Ethereum
Golang execution layer implementation of the Ethereum protocol.
@@ -88,7 +450,7 @@ This command will:
This tool is optional and if you leave it out you can always attach it to an already running
`geth` instance with `geth attach`.
-### A Full node on the Holesky test network
+### A Full node on the Görli test network
Transitioning towards developers, if you'd like to play around with creating Ethereum
contracts, you almost certainly would like to do that without any real money involved until
@@ -97,23 +459,23 @@ network, you want to join the **test** network with your node, which is fully eq
the main network, but with play-Ether only.
```shell
-$ geth --holesky console
+$ geth --goerli console
```
The `console` subcommand has the same meaning as above and is equally
useful on the testnet too.
-Specifying the `--holesky` flag, however, will reconfigure your `geth` instance a bit:
+Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit:
- * Instead of connecting to the main Ethereum network, the client will connect to the Holesky
+ * Instead of connecting to the main Ethereum network, the client will connect to the Görli
test network, which uses different P2P bootnodes, different network IDs and genesis
states.
* Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth`
- will nest itself one level deeper into a `holesky` subfolder (`~/.ethereum/holesky` on
+ will nest itself one level deeper into a `goerli` subfolder (`~/.ethereum/goerli` on
Linux). Note, on OSX and Linux this also means that attaching to a running testnet node
requires the use of a custom endpoint since `geth attach` will try to attach to a
production node endpoint by default, e.g.,
- `geth attach /holesky/geth.ipc`. Windows users are not affected by
+ `geth attach /goerli/geth.ipc`. Windows users are not affected by
this.
*Note: Although some internal protective measures prevent transactions from
diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index a947f35f2..d988135b4 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -111,6 +111,10 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.LogNoHistoryFlag,
utils.LogExportCheckpointsFlag,
utils.StateHistoryFlag,
+ utils.BlockReplicationTargetsFlag,
+ utils.ReplicaEnableSpecimenFlag,
+ utils.ReplicaEnableResultFlag,
+ utils.ReplicaEnableBlobFlag,
}, utils.DatabaseFlags, debug.Flags),
Before: func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)
@@ -301,6 +305,7 @@ func importChain(ctx *cli.Context) error {
}
stack, cfg := makeConfigNode(ctx)
defer stack.Close()
+ replicators := utils.CreateReplicators(&cfg.Eth)
// Start metrics export if enabled
utils.SetupMetrics(&cfg.Metrics)
@@ -308,6 +313,8 @@ func importChain(ctx *cli.Context) error {
chain, db := utils.MakeChain(ctx, stack, false)
defer db.Close()
+ utils.AttachReplicators(replicators, chain)
+
// Start periodically gathering memory profiles
var peakMemAlloc, peakMemSys atomic.Uint64
go func() {
@@ -345,6 +352,7 @@ func importChain(ctx *cli.Context) error {
}
}
chain.Stop()
+ utils.DrainReplicators(replicators)
fmt.Printf("Import done in %v.\n\n", time.Since(start))
// Output pre-compaction stats mostly to see the import trashing
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 421540323..1b173005a 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig"
+ "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log"
@@ -181,7 +182,7 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
}
// makeFullNode loads geth configuration and creates the Ethereum backend.
-func makeFullNode(ctx *cli.Context) *node.Node {
+func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
stack, cfg := makeConfigNode(ctx)
if ctx.IsSet(utils.OverridePrague.Name) {
v := ctx.Uint64(utils.OverridePrague.Name)
@@ -253,7 +254,7 @@ func makeFullNode(ctx *cli.Context) *node.Node {
utils.Fatalf("failed to register catalyst service: %v", err)
}
}
- return stack
+ return stack, backend
}
// dumpConfig is the dumpconfig command.
diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go
index bf38c8634..44598206a 100644
--- a/cmd/geth/consolecmd.go
+++ b/cmd/geth/consolecmd.go
@@ -70,8 +70,8 @@ JavaScript API. See https://geth.ethereum.org/docs/interacting-with-geth/javascr
func localConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags
prepare(ctx)
- stack := makeFullNode(ctx)
- startNode(ctx, stack, true)
+ stack, backend := makeFullNode(ctx)
+ startNode(ctx, stack, true, backend)
defer stack.Close()
// Attach to the newly started node and create the JavaScript console.
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 289030ae6..82347940f 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug"
+ "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
@@ -157,6 +158,10 @@ var (
utils.BeaconGenesisTimeFlag,
utils.BeaconCheckpointFlag,
utils.BeaconCheckpointFileFlag,
+ utils.BlockReplicationTargetsFlag,
+ utils.ReplicaEnableResultFlag,
+ utils.ReplicaEnableSpecimenFlag,
+ utils.ReplicaEnableBlobFlag,
}, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{
@@ -343,17 +348,17 @@ func geth(ctx *cli.Context) error {
}
prepare(ctx)
- stack := makeFullNode(ctx)
+ stack, backend := makeFullNode(ctx)
defer stack.Close()
- startNode(ctx, stack, false)
+ startNode(ctx, stack, false, backend)
stack.Wait()
return nil
}
// startNode boots up the system node and all registered protocols, after which
// it starts the RPC/IPC interfaces and the miner.
-func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
+func startNode(ctx *cli.Context, stack *node.Node, isConsole bool, backend ethapi.Backend) {
// Start up the node itself
utils.StartNode(ctx, stack, isConsole)
@@ -402,6 +407,38 @@ func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
}
}()
+ // Kill bsp-geth if --syncmode flag is 'light'
+ if ctx.String(utils.BlockReplicationTargetsFlag.Name) != "" && ctx.String(utils.SyncModeFlag.Name) == "light" {
+ utils.Fatalf("Block specimen production not supported for 'light' sync (only supported modes are 'snap' and 'full'")
+ }
+
+ // Spawn a standalone goroutine for status synchronization monitoring,
+ // if full sync is completed in block specimen creation mode set replica config flag
+ if ctx.Bool(utils.ReplicaEnableSpecimenFlag.Name) || ctx.Bool(utils.ReplicaEnableResultFlag.Name) {
+ //log.Info("Synchronisation started, historical blocks synced set to 0")
+ backend.SetHistoricalBlocksSynced()
+
+ go func() {
+ sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
+ defer sub.Unsubscribe()
+ for {
+ event := <-sub.Chan()
+ if event == nil {
+ continue
+ }
+ done, ok := event.Data.(downloader.DoneEvent)
+ if !ok {
+ continue
+ }
+ if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
+ log.Info("Synchronisation completed, setting historical blocks synced to 1", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
+ "age", common.PrettyAge(timestamp))
+ backend.SetHistoricalBlocksSynced()
+ }
+ }
+ }()
+ }
+
// Spawn a standalone goroutine for status synchronization monitoring,
// close the node when synchronization is complete if user required.
if ctx.Bool(utils.ExitWhenSyncedFlag.Name) {
diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go
index 2d31f3abe..64d1dbddf 100644
--- a/cmd/geth/misccmd.go
+++ b/cmd/geth/misccmd.go
@@ -23,6 +23,7 @@ import (
"strings"
"github.com/ethereum/go-ethereum/internal/version"
+ "github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli/v2"
)
@@ -73,6 +74,7 @@ func printVersion(ctx *cli.Context) error {
fmt.Println(strings.Title(clientIdentifier))
fmt.Println("Version:", version.WithMeta)
+ fmt.Println("Bsp Version:", params.BspVersion)
if git.Commit != "" {
fmt.Println("Git Commit:", git.Commit)
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index b26f43b37..3d6902157 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -28,6 +28,7 @@ import (
"math/big"
"net"
"net/http"
+ "net/url"
"os"
"path/filepath"
godebug "runtime/debug"
@@ -755,6 +756,23 @@ var (
Value: node.DefaultConfig.BatchResponseMaxSize,
Category: flags.APICategory,
}
+ BlockReplicationTargetsFlag = &cli.StringFlag{
+ Name: "replication.targets",
+ Usage: "Comma separated URLs for message-queue delivery of block specimens",
+ Value: "",
+ }
+ ReplicaEnableSpecimenFlag = &cli.BoolFlag{
+ Name: "replica.specimen",
+ Usage: "Enables export of fields that comprise a block-specimen",
+ }
+ ReplicaEnableResultFlag = &cli.BoolFlag{
+ Name: "replica.result",
+ Usage: "Enables export of fields that comprise a block-result",
+ }
+ ReplicaEnableBlobFlag = &cli.BoolFlag{
+ Name: "replica.blob",
+ Usage: "Enables export of fields that comprise a block-blob",
+ }
// Network Settings
MaxPeersFlag = &cli.IntFlag{
@@ -1583,7 +1601,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setMiner(ctx, &cfg.Miner)
setRequiredBlocks(ctx, cfg)
setLes(ctx, cfg)
-
+ if ctx.IsSet(BlockReplicationTargetsFlag.Name) {
+ setBlockReplicationTargets(ctx, cfg)
+ }
// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()
if err == nil {
@@ -2291,3 +2311,63 @@ func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, read
}
return triedb.NewDatabase(disk, config)
}
+
+// setBlockResultTargets creates a list of replication targets from the command line flags.
+func setBlockReplicationTargets(ctx *cli.Context, cfg *eth.Config) {
+ var urls []string
+
+ if ctx.IsSet(BlockReplicationTargetsFlag.Name) {
+ urls = strings.Split(ctx.String(BlockReplicationTargetsFlag.Name), ",")
+ }
+
+ cfg.BlockReplicationTargets = make([]string, 0, len(urls))
+ for _, urlStr := range urls {
+ if urlStr != "" {
+ _, err := url.Parse(urlStr)
+ if err != nil {
+ log.Crit("Replication-target URL invalid", "url", urlStr, "err", err)
+ os.Exit(1)
+ }
+ cfg.BlockReplicationTargets = append(cfg.BlockReplicationTargets, urlStr)
+ }
+ }
+ if ctx.IsSet(ReplicaEnableResultFlag.Name) || ctx.IsSet(ReplicaEnableSpecimenFlag.Name) {
+ if ctx.Bool(ReplicaEnableSpecimenFlag.Name) {
+ cfg.ReplicaEnableSpecimen = true
+ }
+ if ctx.Bool(ReplicaEnableResultFlag.Name) {
+ cfg.ReplicaEnableResult = true
+ }
+ if ctx.Bool(ReplicaEnableBlobFlag.Name) {
+ cfg.ReplicaEnableBlob = true
+ }
+ } else {
+ Fatalf("--replication.targets flag is invalid without --replica.specimen and/or --replica.result, ONLY ADD --replica.blob with both replica.specimen AND replica.result flags for complete unified state capture)")
+ }
+}
+
+func CreateReplicators(config *eth.Config) []*core.ChainReplicator {
+ replicators := make([]*core.ChainReplicator, 0)
+
+ for _, blockReplicationTargets := range config.BlockReplicationTargets {
+ blockRepl, err := eth.CreateReplicator(blockReplicationTargets)
+ if err != nil {
+ Fatalf("Can't create replication target: %v", err)
+ }
+ replicators = append(replicators, blockRepl)
+ }
+
+ return replicators
+}
+
+func AttachReplicators(replicators []*core.ChainReplicator, chain *core.BlockChain) {
+ for _, replicator := range replicators {
+ replicator.Start(chain, chain.ReplicaConfig)
+ }
+}
+
+func DrainReplicators(replicators []*core.ChainReplicator) {
+ for _, replicator := range replicators {
+ replicator.Stop()
+ }
+}
diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go
index d3201850a..d6b6b867f 100644
--- a/common/hexutil/hexutil.go
+++ b/common/hexutil/hexutil.go
@@ -34,11 +34,10 @@ import (
"encoding/hex"
"fmt"
"math/big"
+ "math/bits"
"strconv"
)
-const uintBits = 32 << (uint64(^uint(0)) >> 63)
-
// Errors
var (
ErrEmptyString = &decError{"empty hex string"}
@@ -48,7 +47,7 @@ var (
ErrEmptyNumber = &decError{"hex string \"0x\""}
ErrLeadingZero = &decError{"hex number with leading zero digits"}
ErrUint64Range = &decError{"hex number > 64 bits"}
- ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)}
+ ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", bits.UintSize)}
ErrBig256Range = &decError{"hex number > 256 bits"}
)
diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go
index 7cca30095..a01443845 100644
--- a/common/hexutil/json_test.go
+++ b/common/hexutil/json_test.go
@@ -22,6 +22,7 @@ import (
"encoding/json"
"errors"
"math/big"
+ "math/bits"
"testing"
"github.com/holiman/uint256"
@@ -384,7 +385,7 @@ func TestUnmarshalUint(t *testing.T) {
for _, test := range unmarshalUintTests {
var v Uint
err := json.Unmarshal([]byte(test.input), &v)
- if uintBits == 32 && test.wantErr32bit != nil {
+ if bits.UintSize == 32 && test.wantErr32bit != nil {
checkError(t, test.input, err, test.wantErr32bit)
continue
}
diff --git a/core/block_replica.go b/core/block_replica.go
new file mode 100644
index 000000000..b8229e77a
--- /dev/null
+++ b/core/block_replica.go
@@ -0,0 +1,208 @@
+package core
+
+import (
+ "bytes"
+ "fmt"
+ "math/big"
+ "sync/atomic"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+type BlockReplicationEvent struct {
+ Hash string
+ Data []byte
+}
+
+func (bc *BlockChain) createBlockReplica(block *types.Block, replicaConfig *ReplicaConfig, chainConfig *params.ChainConfig, stateSpecimen *types.StateSpecimen) error {
+
+ // blobs
+ var blobTxSidecars []*types.BlobTxSidecar
+ // if replicaConfig.EnableBlob {
+ // for sidecarData := range types.BlobTxSidecarChan {
+ // if sidecarData.BlockNumber.Uint64() == block.NumberU64() {
+ // log.Info("Consuming BlobTxSidecar Match From Chain Sync Channel", "Block Number:", sidecarData.BlockNumber.Uint64())
+ // blobTxSidecars = append(blobTxSidecars, sidecarData.Blobs)
+ // } else {
+ // log.Info("Failing BlobTxSidecar Match from Chain Sync Channel", "Block Number:", sidecarData.BlockNumber.Uint64())
+ // }
+ // log.Info("BlobTxSidecar Header", "Block Number:", sidecarData.BlockNumber.Uint64())
+ // log.Info("Chain Sync Sidecar Channel", "Length:", len(types.BlobTxSidecarChan))
+ // }
+ // }
+ //block replica with blobs
+ exportBlockReplica, err := bc.createReplica(block, replicaConfig, chainConfig, stateSpecimen, blobTxSidecars)
+ if err != nil {
+ return err
+ }
+ //encode to rlp
+ blockReplicaRLP, err := rlp.EncodeToBytes(exportBlockReplica)
+ if err != nil {
+ log.Error("error encoding block replica rlp", "error", err)
+ return err
+ }
+
+ sHash := block.Hash().String()
+
+ if atomic.LoadUint32(replicaConfig.HistoricalBlocksSynced) == 0 {
+ log.Info("BSP running in Live mode", "Unexported block ", block.NumberU64(), "hash", sHash)
+ return nil
+ } else if atomic.LoadUint32(replicaConfig.HistoricalBlocksSynced) == 1 {
+ log.Info("Creating Block Specimen", "Exported block", block.NumberU64(), "hash", sHash)
+ bc.blockReplicationFeed.Send(BlockReplicationEvent{
+ sHash,
+ blockReplicaRLP,
+ })
+ return nil
+ } else {
+ return fmt.Errorf("error in setting atomic config historical block sync: %v", replicaConfig.HistoricalBlocksSynced)
+ }
+}
+
+func (bc *BlockChain) createReplica(block *types.Block, replicaConfig *ReplicaConfig, chainConfig *params.ChainConfig, stateSpecimen *types.StateSpecimen, blobSpecimen []*types.BlobTxSidecar) (*types.ExportBlockReplica, error) {
+ log.Info("Creating Block Replica", "Block Number:", block.NumberU64(), "Block Hash:", block.Hash().String())
+ bHash := block.Hash()
+ bNum := block.NumberU64()
+
+ //totalDifficulty
+ //tdRLP := rawdb.ReadTdRLP(bc.db, bHash, bNum)
+ td := new(big.Int)
+ td.SetUint64(0)
+ //if err := rlp.Decode(bytes.NewReader(tdRLP), td); err != nil {
+ // log.Error("Invalid block total difficulty RLP ", "hash ", bHash, "err", err)
+ // return nil, err
+ //}
+
+ //header
+ headerRLP := rawdb.ReadHeaderRLP(bc.db, bHash, bNum)
+ header := new(types.Header)
+ if err := rlp.Decode(bytes.NewReader(headerRLP), header); err != nil {
+ log.Error("Invalid block header RLP ", "hash ", bHash, "err ", err)
+ return nil, err
+ }
+
+ //transactions
+ txsExp := make([]*types.TransactionForExport, len(block.Transactions()))
+ txsRlp := make([]*types.TransactionExportRLP, len(block.Transactions()))
+ for i, tx := range block.Transactions() {
+ txsExp[i] = (*types.TransactionForExport)(tx)
+ txsRlp[i] = txsExp[i].ExportTx(chainConfig, block.Number(), header.BaseFee, header.Time)
+ if !replicaConfig.EnableSpecimen {
+ txsRlp[i].V, txsRlp[i].R, txsRlp[i].S = nil, nil, nil
+ }
+ }
+
+ // withdrawals
+ var withdrawalsRlp []*types.WithdrawalExportRLP = nil
+ if chainConfig.IsShanghai(block.Number(), block.Time()) {
+ withdrawalsExp := make([]*types.WithdrawalForExport, len(block.Withdrawals()))
+ withdrawalsRlp = make([]*types.WithdrawalExportRLP, len(block.Withdrawals()))
+ for i, withdrawal := range block.Withdrawals() {
+ withdrawalsExp[i] = (*types.WithdrawalForExport)(withdrawal)
+ withdrawalsRlp[i] = withdrawalsExp[i].ExportWithdrawal()
+ }
+ }
+
+ //receipts
+ receipts := rawdb.ReadRawReceipts(bc.db, bHash, bNum)
+ receiptsExp := make([]*types.ReceiptForExport, len(receipts))
+ receiptsRlp := make([]*types.ReceiptExportRLP, len(receipts))
+ for i, receipt := range receipts {
+ receiptsExp[i] = (*types.ReceiptForExport)(receipt)
+ receiptsRlp[i] = receiptsExp[i].ExportReceipt()
+ }
+
+ //senders
+ signer := types.MakeSigner(bc.chainConfig, block.Number(), block.Time())
+ senders := make([]common.Address, 0, len(block.Transactions()))
+ for _, tx := range block.Transactions() {
+ sender, err := types.Sender(signer, tx)
+ if err != nil {
+ return nil, err
+ } else {
+ senders = append(senders, sender)
+ }
+ }
+
+ //uncles
+ uncles := block.Uncles()
+
+ //block replica export
+ if replicaConfig.EnableSpecimen && replicaConfig.EnableResult && replicaConfig.EnableBlob {
+ exportBlockReplica := &types.ExportBlockReplica{
+ Type: "block-replica",
+ NetworkId: chainConfig.ChainID.Uint64(),
+ Hash: bHash,
+ TotalDiff: td,
+ Header: header,
+ Transactions: txsRlp,
+ Uncles: uncles,
+ Receipts: receiptsRlp,
+ Senders: senders,
+ State: stateSpecimen,
+ Withdrawals: withdrawalsRlp,
+ BlobTxSidecars: []*types.BlobTxSidecar{},
+ }
+ log.Debug("Exporting full block-replica with blob-specimen")
+ return exportBlockReplica, nil
+ } else if replicaConfig.EnableSpecimen && !replicaConfig.EnableResult {
+ exportBlockReplica := &types.ExportBlockReplica{
+ Type: "block-specimen",
+ NetworkId: chainConfig.ChainID.Uint64(),
+ Hash: bHash,
+ TotalDiff: td,
+ Header: header,
+ Transactions: txsRlp,
+ Uncles: uncles,
+ Receipts: []*types.ReceiptExportRLP{},
+ Senders: senders,
+ State: stateSpecimen,
+ Withdrawals: withdrawalsRlp,
+ BlobTxSidecars: []*types.BlobTxSidecar{},
+ }
+ log.Debug("Exporting block-specimen only (no blob specimens)")
+ return exportBlockReplica, nil
+ } else if !replicaConfig.EnableSpecimen && replicaConfig.EnableResult {
+ exportBlockReplica := &types.ExportBlockReplica{
+ Type: "block-result",
+ NetworkId: chainConfig.ChainID.Uint64(),
+ Hash: bHash,
+ TotalDiff: td,
+ Header: header,
+ Transactions: txsRlp,
+ Uncles: uncles,
+ Receipts: receiptsRlp,
+ Senders: senders,
+ State: &types.StateSpecimen{},
+ BlobTxSidecars: []*types.BlobTxSidecar{},
+ }
+ log.Debug("Exporting block-result only (no blob specimens)")
+ return exportBlockReplica, nil
+ } else {
+ return nil, fmt.Errorf("--replication.targets flag is invalid without --replica.specimen and/or --replica.result, ADD --replica.blob with both replica.specimen AND replica.result flags for complete unified state capture aka block-replica)")
+ }
+}
+
+// SubscribeChainReplicationEvent registers a subscription of ChainReplicationEvent.
+func (bc *BlockChain) SubscribeBlockReplicationEvent(ch chan<- BlockReplicationEvent) event.Subscription {
+ return bc.scope.Track(bc.blockReplicationFeed.Subscribe(ch))
+}
+
+func (bc *BlockChain) SetBlockReplicaExports(replicaConfig *ReplicaConfig) bool {
+ if replicaConfig.EnableResult {
+ bc.ReplicaConfig.EnableResult = true
+ }
+ if replicaConfig.EnableSpecimen {
+ bc.ReplicaConfig.EnableSpecimen = true
+ }
+ if replicaConfig.EnableBlob {
+ bc.ReplicaConfig.EnableBlob = true
+ }
+ return true
+}
diff --git a/core/blockchain.go b/core/blockchain.go
index 6667f6491..a96ff878d 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -281,7 +281,16 @@ type BlockChain struct {
vmConfig vm.Config
logger *tracing.Hooks
- lastForkReadyAlert time.Time // Last time there was a fork readiness print out
+ lastForkReadyAlert time.Time // Last time there was a fork readiness print out
+ blockReplicationFeed event.Feed
+ ReplicaConfig *ReplicaConfig
+}
+
+type ReplicaConfig struct {
+ EnableSpecimen bool
+ EnableResult bool
+ EnableBlob bool
+ HistoricalBlocksSynced *uint32
}
// NewBlockChain returns a fully initialised block chain using information
@@ -315,21 +324,28 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
log.Info("")
bc := &BlockChain{
- chainConfig: chainConfig,
- cacheConfig: cacheConfig,
- db: db,
- triedb: triedb,
- triegc: prque.New[int64, common.Hash](nil),
- quit: make(chan struct{}),
- chainmu: syncx.NewClosableMutex(),
- bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
- bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
- receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
- blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
- txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
- engine: engine,
- vmConfig: vmConfig,
- logger: vmConfig.Tracer,
+ chainConfig: chainConfig,
+ cacheConfig: cacheConfig,
+ db: db,
+ triedb: triedb,
+ triegc: prque.New[int64, common.Hash](nil),
+ quit: make(chan struct{}),
+ chainmu: syncx.NewClosableMutex(),
+ bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
+ bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
+ receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
+ blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
+ txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
+ engine: engine,
+ vmConfig: vmConfig,
+ logger: vmConfig.Tracer,
+ blockReplicationFeed: event.Feed{},
+ ReplicaConfig: &ReplicaConfig{
+ EnableSpecimen: false,
+ EnableResult: false,
+ EnableBlob: false,
+ HistoricalBlocksSynced: new(uint32), // Always set 0 for historical mode at start
+ },
}
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
if err != nil {
@@ -1835,7 +1851,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
if err != nil {
return nil, it.index, err
}
-
+ // Enable prefetching to pull in trie node paths while processing transactions
+ statedb.EnableStateSpecimenTracking()
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
@@ -1895,6 +1912,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
bc.logForkReadiness(block)
if !setHead {
+ // Export Block Specimen
+ if bc.ReplicaConfig.EnableSpecimen || bc.ReplicaConfig.EnableResult {
+ bc.createBlockReplica(block, bc.ReplicaConfig, bc.chainConfig, statedb.TakeStateSpecimen())
+ }
// After merge we expect few side chains. Simply count
// all blocks the CL gives us for GC processing time
bc.gcproc += res.procTime
@@ -1906,7 +1927,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
"elapsed", common.PrettyDuration(time.Since(start)),
"root", block.Root())
-
+ // Handle creation of block specimen for canonical blocks
+ if bc.ReplicaConfig.EnableSpecimen || bc.ReplicaConfig.EnableResult {
+ bc.createBlockReplica(block, bc.ReplicaConfig, bc.chainConfig, statedb.TakeStateSpecimen())
+ }
lastCanon = block
// Only count canonical blocks for GC processing time
@@ -1917,6 +1941,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
"root", block.Root())
+ // Currently proof-chain is not handling the forked block use case hence commented out
+ //bc.createBlockReplica(block, bc.ReplicaConfig, bc.chainConfig, statedb.TakeStateSpecimen())
default:
// This in theory is impossible, but lets be nice to our future selves and leave
@@ -1926,6 +1952,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
"root", block.Root())
}
+ // Is impossible but keeping in line to be nice to our future selves we add this for now
+ if bc.ReplicaConfig.EnableSpecimen || bc.ReplicaConfig.EnableResult {
+ bc.createBlockReplica(block, bc.ReplicaConfig, bc.chainConfig, statedb.TakeStateSpecimen())
+ }
}
stats.ignored += it.remaining()
diff --git a/core/chain_replication.go b/core/chain_replication.go
new file mode 100644
index 000000000..a4072393f
--- /dev/null
+++ b/core/chain_replication.go
@@ -0,0 +1,326 @@
+package core
+
+import (
+ "context"
+ "errors"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type ChainReplicationBackend interface {
+ Process(ctx context.Context, events []*BlockReplicationEvent) error
+ String() string
+}
+
+// ChainReplicationChain interface is used for connecting the replicator to a blockchain
+type ChainReplicatorChain interface {
+ // SubscribeChainReplicationEvent subscribes to new replication notifications.
+ SubscribeBlockReplicationEvent(ch chan<- BlockReplicationEvent) event.Subscription
+ // Set Block replica export types
+ SetBlockReplicaExports(replicaConfig *ReplicaConfig) bool
+}
+
+type ChainReplicator struct {
+ sessionId uint64
+
+ backend ChainReplicationBackend
+
+ mode uint32
+ modeLock sync.Mutex
+ drain chan struct{}
+ exitStatus chan error
+ ctx context.Context
+ ctxCancel func()
+
+ log *replicationLogger
+}
+
+var replicationSessionSeq uint64
+
+func NewChainReplicator(backend ChainReplicationBackend) *ChainReplicator {
+ sessionId := atomic.AddUint64(&replicationSessionSeq, 1)
+
+ c := &ChainReplicator{
+ sessionId: sessionId,
+ backend: backend,
+ drain: make(chan struct{}),
+ exitStatus: make(chan error),
+ log: &replicationLogger{log: log.New("sessID", sessionId)},
+ }
+
+ c.ctx, c.ctxCancel = context.WithCancel(context.Background())
+
+ return c
+}
+
+const (
+ modeNotStarted uint32 = iota
+ modeStarting
+ modeRunning
+ modeStopping
+)
+
+func (c *ChainReplicator) Start(chain ChainReplicatorChain, replicaConfig *ReplicaConfig) {
+ c.modeLock.Lock()
+ defer c.modeLock.Unlock()
+
+ if !atomic.CompareAndSwapUint32(&c.mode, modeNotStarted, modeStarting) {
+ return
+ }
+
+ c.log.Info("Replication began", "backend", c.backend.String())
+
+ bSEvents := make(chan BlockReplicationEvent, 1000)
+ bSSub := chain.SubscribeBlockReplicationEvent(bSEvents)
+ _ = chain.SetBlockReplicaExports(replicaConfig)
+ go c.eventLoop(bSEvents, bSSub)
+}
+
+func (c *ChainReplicator) Stop() (err error) {
+ c.modeLock.Lock()
+ defer c.modeLock.Unlock()
+
+ if !atomic.CompareAndSwapUint32(&c.mode, modeRunning, modeStopping) {
+ return
+ }
+
+ close(c.drain)
+ err = <-c.exitStatus
+ atomic.StoreUint32(&c.mode, modeNotStarted)
+
+ return
+}
+
+func (c *ChainReplicator) CloseImmediate() (err error) {
+ if atomic.LoadUint32(&c.mode) == modeStopping {
+ // Stop() or another CloseImmediate() is already holding the lock,
+ // so just hurry it along (idempotently)
+ c.ctxCancel()
+
+ // wait for the other task to finish
+ c.modeLock.Lock()
+ defer c.modeLock.Unlock()
+
+ return
+ }
+
+ c.modeLock.Lock()
+ defer c.modeLock.Unlock()
+
+ if !atomic.CompareAndSwapUint32(&c.mode, modeRunning, modeStopping) {
+ return
+ }
+
+ c.ctxCancel()
+ err = <-c.exitStatus
+ atomic.StoreUint32(&c.mode, modeNotStarted)
+
+ return
+}
+
+var (
+ errComplete = errors.New("replication complete")
+ errDraining = errors.New("draining")
+ errUnsubscribed = errors.New("unsubscribed")
+ errContextDone = errors.New("context completed")
+)
+
+func (c *ChainReplicator) eventLoop(events chan BlockReplicationEvent, sub event.Subscription) {
+ defer sub.Unsubscribe()
+ defer close(c.exitStatus)
+ defer c.log.nextSession()
+
+ atomic.StoreUint32(&c.mode, modeRunning)
+
+ var (
+ draining bool
+ unsubbed bool
+
+ flush bool
+ lastFlushTime = time.Now()
+ lastReportTime = lastFlushTime
+
+ ticker = time.NewTicker(1 * time.Second)
+ eventBuf = make([]*BlockReplicationEvent, 0, 500)
+
+ stateChange = make(chan error, 2)
+ loopDone = make(chan struct{})
+ )
+
+ defer ticker.Stop()
+ defer close(loopDone)
+
+ go func() {
+ select {
+ case err, ok := <-sub.Err():
+ if ok {
+ stateChange <- err
+ } else {
+ stateChange <- errUnsubscribed
+ }
+ return
+ case <-loopDone:
+ return
+ }
+ }()
+
+ go func() {
+ select {
+ case <-c.ctx.Done():
+ stateChange <- errContextDone
+ return
+ case <-loopDone:
+ return
+ }
+ }()
+
+ go func() {
+ select {
+ case <-c.drain:
+ stateChange <- errDraining
+ case <-loopDone:
+ return
+ }
+ }()
+
+ for {
+ select {
+ case err := <-stateChange:
+ switch err {
+ case errComplete:
+ c.log.Info("Replication complete")
+ return
+
+ case errDraining:
+ if !draining {
+ c.log.Info("Replication queue draining")
+ draining = true
+ }
+
+ case errUnsubscribed:
+ if !unsubbed {
+ c.log.Debug("Replication producer unsubscribed")
+ unsubbed = true
+ flush = true
+ }
+
+ case errContextDone:
+ c.log.Info("Replication interrupted")
+ return
+
+ default:
+ // a real error, from the subscription producer
+ c.log.Warn("Replication failure", "err", err)
+ c.exitStatus <- err
+ return
+ }
+
+ case ev, ok := <-events:
+ if ok {
+ eventBuf = append(eventBuf, &ev)
+
+ if len(eventBuf) == 500 {
+ flush = true
+ }
+ } else {
+ stateChange <- errUnsubscribed
+ }
+
+ case t := <-ticker.C:
+ if t.Sub(lastReportTime) >= (8 * time.Second) {
+ if len(eventBuf) > 0 || c.log.IsDirty() {
+ c.log.Info("Replication progress", "queued", len(eventBuf))
+ }
+ lastReportTime = t
+ }
+
+ if len(eventBuf) > 0 && t.Sub(lastFlushTime) >= (3*time.Second) {
+ flush = true
+ } else if draining && len(eventBuf) == 0 && t.Sub(lastFlushTime) >= (2*time.Second) {
+ c.log.Info("Replication complete (queue drained)")
+ return
+ }
+ }
+
+ if flush {
+ if len(eventBuf) > 0 {
+ if err := c.backend.Process(c.ctx, eventBuf); err != nil {
+ stateChange <- err
+ } else {
+ c.log.sent(eventBuf)
+ c.log.Debug("Replication segment", "len", len(eventBuf))
+ }
+ }
+
+ flush = false
+ lastFlushTime = time.Now()
+ eventBuf = eventBuf[:0]
+
+ if unsubbed {
+ stateChange <- errComplete
+ unsubbed = false
+ }
+ }
+ }
+}
+
+type replicationLogger struct {
+ log log.Logger
+ flushedCount uint64
+ hashValid bool
+ lastHash string
+ dirty bool
+}
+
+func (rl *replicationLogger) IsDirty() bool {
+ return rl.dirty
+}
+
+func (rl *replicationLogger) appendMetrics(input []interface{}) []interface{} {
+ rl.dirty = false
+
+ if rl.hashValid {
+ return append(input, "sent", rl.flushedCount, "last", rl.lastHash)
+ } else {
+ return append(input, "sent", rl.flushedCount)
+ }
+}
+
+func (rl *replicationLogger) nextSession() {
+ rl.hashValid = false
+ rl.dirty = false
+}
+
+func (rl *replicationLogger) sent(eventBuf []*BlockReplicationEvent) {
+ if len(eventBuf) == 0 {
+ return
+ }
+
+ rl.flushedCount += uint64(len(eventBuf))
+ rl.lastHash = eventBuf[len(eventBuf)-1].Hash
+ rl.hashValid = true
+ rl.dirty = true
+}
+
+func (rl *replicationLogger) Trace(slug string, ctx ...interface{}) {
+ rl.log.Trace(slug, rl.appendMetrics(ctx)...)
+}
+func (rl *replicationLogger) Debug(slug string, ctx ...interface{}) {
+ rl.log.Debug(slug, rl.appendMetrics(ctx)...)
+}
+func (rl *replicationLogger) Info(slug string, ctx ...interface{}) {
+ rl.log.Info(slug, rl.appendMetrics(ctx)...)
+}
+func (rl *replicationLogger) Warn(slug string, ctx ...interface{}) {
+ rl.log.Warn(slug, rl.appendMetrics(ctx)...)
+}
+func (rl *replicationLogger) Error(slug string, ctx ...interface{}) {
+ rl.log.Error(slug, rl.appendMetrics(ctx)...)
+}
+func (rl *replicationLogger) Crit(slug string, ctx ...interface{}) {
+ rl.log.Crit(slug, rl.appendMetrics(ctx)...)
+}
diff --git a/core/state/state_object.go b/core/state/state_object.go
index a6979bd36..0e639b8f0 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -203,6 +203,9 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
}
}
+ if sS := s.db.stateSpecimen; sS != nil {
+ sS.LogStorageRead(s.address, key, value)
+ }
s.originStorage[key] = value
return value
}
@@ -524,6 +527,9 @@ func (s *stateObject) Code() []byte {
if len(code) == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
}
+ if sS := s.db.stateSpecimen; sS != nil {
+ sS.LogCodeRead(s.CodeHash(), code)
+ }
s.code = code
return code
}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index e3f5b9e1a..84df40e9c 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -155,6 +155,9 @@ type StateDB struct {
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
+
+ // Log of state data read from backing DB
+ stateSpecimen *types.StateSpecimen
}
// New creates a new state from a given trie.
@@ -180,10 +183,12 @@ func New(root common.Hash, db Database) (*StateDB, error) {
journal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
+ stateSpecimen: types.NewStateSpecimen(),
}
if db.TrieDB().IsVerkle() {
sdb.accessEvents = NewAccessEvents(db.PointCache())
}
+
return sdb, nil
}
@@ -597,6 +602,8 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
log.Error("Failed to prefetch account", "addr", addr, "err", err)
}
}
+ s.stateSpecimen.LogAccountRead(addr, acct.Nonce, acct.Balance.ToBig(), acct.CodeHash)
+
// Insert into the live set
obj := newObject(s, addr, acct)
s.setStateObject(obj)
@@ -676,6 +683,7 @@ func (s *StateDB) Copy() *StateDB {
accessList: s.accessList.Copy(),
transientStorage: s.transientStorage.Copy(),
journal: s.journal.copy(),
+ stateSpecimen: types.NewStateSpecimen(),
}
if s.witness != nil {
state.witness = s.witness.Copy()
@@ -683,6 +691,9 @@ func (s *StateDB) Copy() *StateDB {
if s.accessEvents != nil {
state.accessEvents = s.accessEvents.Copy()
}
+ if s.stateSpecimen != nil {
+ state.stateSpecimen = s.stateSpecimen.Copy()
+ }
// Deep copy cached state objects.
for addr, obj := range s.stateObjects {
state.stateObjects[addr] = obj.deepCopy(state)
@@ -1434,3 +1445,17 @@ func (s *StateDB) Witness() *stateless.Witness {
func (s *StateDB) AccessEvents() *AccessEvents {
return s.accessEvents
}
+
+func (s *StateDB) EnableStateSpecimenTracking() {
+ s.stateSpecimen = types.NewStateSpecimen()
+}
+
+func (s *StateDB) TakeStateSpecimen() *types.StateSpecimen {
+ sp := s.stateSpecimen
+ sp.BlockhashReadMap = make(map[uint64]common.Hash)
+ return sp
+}
+
+func (s *StateDB) GetStateSpecimen() *types.StateSpecimen {
+ return s.stateSpecimen
+}
diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go
index a2fdfe9a2..79e324364 100644
--- a/core/state/statedb_hooked.go
+++ b/core/state/statedb_hooked.go
@@ -276,3 +276,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
}
}
}
+
+func (s *hookedStateDB) GetStateSpecimen() *types.StateSpecimen {
+ return s.inner.GetStateSpecimen()
+}
diff --git a/core/types/block_export.go b/core/types/block_export.go
new file mode 100644
index 000000000..06e90a9ca
--- /dev/null
+++ b/core/types/block_export.go
@@ -0,0 +1,162 @@
+package types
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+type ExportBlockReplica struct {
+ Type string
+ NetworkId uint64
+ Hash common.Hash
+ TotalDiff *big.Int
+ Header *Header
+ Transactions []*TransactionExportRLP
+ Uncles []*Header
+ Receipts []*ReceiptExportRLP
+ Senders []common.Address
+ State *StateSpecimen
+ Withdrawals []*WithdrawalExportRLP
+ BlobTxSidecars []*BlobTxSidecar
+}
+
+type LogsExportRLP struct {
+ Address common.Address `json:"address"`
+ Topics []common.Hash `json:"topics"`
+ Data []byte `json:"data"`
+ BlockNumber uint64 `json:"blockNumber"`
+ TxHash common.Hash `json:"transactionHash"`
+ TxIndex uint `json:"transactionIndex"`
+ BlockHash common.Hash `json:"blockHash"`
+ Index uint `json:"logIndex"`
+ Removed bool `json:"removed"`
+}
+
+type ReceiptForExport Receipt
+
+type ReceiptExportRLP struct {
+ PostStateOrStatus []byte
+ CumulativeGasUsed uint64
+ TxHash common.Hash
+ ContractAddress common.Address
+ Logs []*LogsExportRLP
+ GasUsed uint64
+}
+
+type WithdrawalForExport Withdrawal
+
+type WithdrawalExportRLP struct {
+ Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer
+ Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal
+ Address common.Address `json:"address"` // target address for withdrawn ether
+ Amount uint64 `json:"amount"` // value of withdrawal in Gwei
+}
+
+type TransactionForExport Transaction
+
+type TransactionExportRLP struct {
+ Type byte `json:"type"`
+ AccessList AccessList `json:"accessList"`
+ ChainId *big.Int `json:"chainId"`
+ AccountNonce uint64 `json:"nonce"`
+ Price *big.Int `json:"gasPrice"`
+ GasLimit uint64 `json:"gas"`
+ GasTipCap *big.Int `json:"gasTipCap"`
+ GasFeeCap *big.Int `json:"gasFeeCap"`
+ Sender *common.Address `json:"from" rlp:"nil"`
+ Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
+ Amount *big.Int `json:"value"`
+ Payload []byte `json:"input"`
+ V *big.Int `json:"v" rlp:"nil"`
+ R *big.Int `json:"r" rlp:"nil"`
+ S *big.Int `json:"s" rlp:"nil"`
+ BlobFeeCap *big.Int `json:"blobFeeCap" rlp:"optional"`
+ BlobHashes []common.Hash `json:"blobHashes" rlp:"optional"`
+ BlobGas uint64 `json:"blobGas" rlp:"optional"`
+}
+
+type BlobTxSidecarData struct {
+ Blobs *BlobTxSidecar
+ BlockNumber *big.Int
+}
+
+var BlobTxSidecarChan = make(chan *BlobTxSidecarData, 100)
+
+func (r *ReceiptForExport) ExportReceipt() *ReceiptExportRLP {
+ enc := &ReceiptExportRLP{
+ PostStateOrStatus: (*Receipt)(r).statusEncoding(),
+ GasUsed: r.GasUsed,
+ CumulativeGasUsed: r.CumulativeGasUsed,
+ TxHash: r.TxHash,
+ ContractAddress: r.ContractAddress,
+ Logs: make([]*LogsExportRLP, len(r.Logs)),
+ }
+ for i, log := range r.Logs {
+ enc.Logs[i] = (*LogsExportRLP)(log)
+ }
+ return enc
+}
+
+func (r *WithdrawalForExport) ExportWithdrawal() *WithdrawalExportRLP {
+ return &WithdrawalExportRLP{
+ Index: r.Index,
+ Validator: r.Validator,
+ Address: r.Address,
+ Amount: r.Amount,
+ }
+}
+
+func (tx *TransactionForExport) ExportTx(chainConfig *params.ChainConfig, blockNumber *big.Int, baseFee *big.Int, blockTime uint64) *TransactionExportRLP {
+ var inner_tx *Transaction = (*Transaction)(tx)
+ v, r, s := tx.inner.rawSignatureValues()
+ var signer Signer = MakeSigner(chainConfig, blockNumber, blockTime)
+ from, _ := Sender(signer, inner_tx)
+
+ txData := tx.inner
+
+ if inner_tx.Type() == BlobTxType {
+ return &TransactionExportRLP{
+ AccountNonce: txData.nonce(),
+ Price: txData.effectiveGasPrice(&big.Int{}, baseFee),
+ GasLimit: txData.gas(),
+ Sender: &from,
+ Recipient: txData.to(),
+ Amount: txData.value(),
+ Payload: txData.data(),
+ Type: txData.txType(),
+ ChainId: txData.chainID(),
+ AccessList: txData.accessList(),
+ GasTipCap: txData.gasTipCap(),
+ GasFeeCap: txData.gasFeeCap(),
+ V: v,
+ R: r,
+ S: s,
+ BlobFeeCap: inner_tx.BlobGasFeeCap(),
+ BlobHashes: inner_tx.BlobHashes(),
+ BlobGas: inner_tx.BlobGas(),
+ }
+ } else {
+ return &TransactionExportRLP{
+ AccountNonce: txData.nonce(),
+ Price: txData.effectiveGasPrice(&big.Int{}, baseFee),
+ GasLimit: txData.gas(),
+ Sender: &from,
+ Recipient: txData.to(),
+ Amount: txData.value(),
+ Payload: txData.data(),
+ Type: txData.txType(),
+ ChainId: txData.chainID(),
+ AccessList: txData.accessList(),
+ GasTipCap: txData.gasTipCap(),
+ GasFeeCap: txData.gasFeeCap(),
+ V: v,
+ R: r,
+ S: s,
+ BlobFeeCap: &big.Int{},
+ BlobHashes: make([]common.Hash, 0),
+ BlobGas: 0,
+ }
+ }
+}
diff --git a/core/types/block_specimen.go b/core/types/block_specimen.go
new file mode 100644
index 000000000..47587c80c
--- /dev/null
+++ b/core/types/block_specimen.go
@@ -0,0 +1,110 @@
+package types
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type StateSpecimen struct {
+ AccountRead []*accountRead
+ StorageRead []*storageRead
+ CodeRead []*codeRead
+ BlockhashRead []*blockhashRead
+ BlockhashReadMap map[uint64]common.Hash `json:"-" rlp:"-"` // ignore in encoding/decoding
+}
+
+type accountRead struct {
+ Address common.Address
+ Nonce uint64
+ Balance *big.Int
+ CodeHash common.Hash
+}
+
+type storageRead struct {
+ Account common.Address
+ SlotKey common.Hash
+ Value common.Hash
+}
+
+type codeRead struct {
+ Hash common.Hash
+ Code []byte
+}
+
+type blockhashRead struct {
+ BlockNumber uint64
+ BlockHash common.Hash
+}
+
+func NewStateSpecimen() *StateSpecimen {
+ sp := &StateSpecimen{
+ BlockhashReadMap: make(map[uint64]common.Hash),
+ }
+ return sp
+}
+
+func (sp *StateSpecimen) Copy() *StateSpecimen {
+ cpy := StateSpecimen{
+ AccountRead: make([]*accountRead, 0),
+ StorageRead: make([]*storageRead, 0),
+ CodeRead: make([]*codeRead, 0),
+ BlockhashRead: make([]*blockhashRead, 0),
+ BlockhashReadMap: make(map[uint64]common.Hash),
+ }
+
+ return &cpy
+}
+
+func (sp *StateSpecimen) LogAccountRead(addr common.Address, nonce uint64, balance *big.Int, codeHashB []byte) *StateSpecimen {
+ codeHash := common.BytesToHash(codeHashB)
+ log.Trace("Retrieved committed account", "addr", addr, "nonce", nonce, "balance", balance, "codeHash", codeHash)
+
+ sp.AccountRead = append(sp.AccountRead, &accountRead{
+ Address: addr,
+ Nonce: nonce,
+ Balance: balance,
+ CodeHash: codeHash,
+ })
+
+ return sp
+}
+
+func (sp *StateSpecimen) LogStorageRead(account common.Address, slotKey common.Hash, value common.Hash) *StateSpecimen {
+ log.Trace("Retrieved committed storage", "account", account, "slotKey", slotKey, "value", value)
+
+ sp.StorageRead = append(sp.StorageRead, &storageRead{
+ Account: account,
+ SlotKey: slotKey,
+ Value: value,
+ })
+
+ return sp
+}
+
+func (sp *StateSpecimen) LogCodeRead(hashB []byte, code []byte) *StateSpecimen {
+ hash := common.BytesToHash(hashB)
+ log.Trace("Retrieved code", "hash", hash, "len", len(code))
+
+ sp.CodeRead = append(sp.CodeRead, &codeRead{
+ Hash: hash,
+ Code: code,
+ })
+
+ return sp
+}
+
+func (sp *StateSpecimen) LogBlockhashRead(blockN uint64, blockHash common.Hash) *StateSpecimen {
+ log.Trace("Retrieved BlockHash", "block_number", blockN, "hash", blockHash)
+
+ if _, ok := sp.BlockhashReadMap[blockN]; !ok {
+ sp.BlockhashReadMap[blockN] = blockHash
+ sp.BlockhashRead = append(sp.BlockhashRead, &blockhashRead{
+ BlockNumber: blockN,
+ BlockHash: blockHash,
+ })
+ }
+
+ return sp
+}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 0b3b1d156..553871ab1 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -443,8 +443,9 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
} else {
lower = upper - 256
}
+ var res common.Hash
if num64 >= lower && num64 < upper {
- res := interpreter.evm.Context.GetHash(num64)
+ res = interpreter.evm.Context.GetHash(num64)
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
witness.AddBlockHash(num64)
}
@@ -455,6 +456,9 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
} else {
num.Clear()
}
+ if specimen := interpreter.evm.StateDB.GetStateSpecimen(); specimen != nil {
+ specimen.LogBlockhashRead(num64, res)
+ }
return nil, nil
}
diff --git a/core/vm/interface.go b/core/vm/interface.go
index 57f35cb24..d3b82ab55 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -101,4 +101,5 @@ type StateDB interface {
// Finalise must be invoked at the end of a transaction
Finalise(bool)
+ GetStateSpecimen() *types.StateSpecimen
}
diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml
new file mode 100644
index 000000000..e25400536
--- /dev/null
+++ b/docker-compose-ci.yml
@@ -0,0 +1,119 @@
+version: '3'
+
+services:
+ redis:
+ image: redis:alpine
+ container_name: redis-srv
+ restart: always
+ expose:
+ - 6379
+ environment:
+ - REDIS_REPLICATION_MODE=master
+ networks:
+ - cqt-net
+ entrypoint: redis-server #/usr/local/etc/redis/redis.conf
+ ports:
+ - "6379:6379"
+
+ redis-commander:
+ image: rediscommander/redis-commander:latest
+ container_name: redis-commander-web
+ hostname: redis-commander
+ restart: always
+ depends_on:
+ - redis
+ environment:
+ - REDIS_HOSTS=local:redis:6379
+ networks:
+ - cqt-net
+ ports:
+ - "8081:8081"
+
+ node:
+ image: trufflesuite/ganache-cli:v6.12.2
+ container_name: ganache-cli
+ restart: always
+ entrypoint:
+ - node
+ - /app/ganache-core.docker.cli.js
+ - --deterministic
+ - --db=/ganache_data
+ - --mnemonic
+ - 'minimum symptom minute gloom tragic situate silver mechanic salad amused elite beef'
+ - --networkId
+ - '5777'
+ - --hostname
+ - '0.0.0.0'
+ depends_on:
+ - redis-commander
+ networks:
+ - cqt-net
+ ports:
+ - "8545:8545"
+
+ cqt-virtnet:
+ image: "ghcr.io/covalenthq/cqt-virtnet:latest"
+ container_name: proof-chain
+ restart: on-failure
+ expose:
+ - 8008
+ entrypoint: >
+ /bin/bash -l -c "
+ truffle migrate --network docker;
+ nc -v agent 8008;
+ sleep 100000;"
+ depends_on:
+ - node
+ networks:
+ - cqt-net
+ environment:
+ npm_config_user: "root"
+ ports:
+ - "8008:8008"
+
+ geth:
+ container_name: bsp-geth
+ build:
+ context: .
+ dockerfile: Dockerfile
+ restart: on-failure
+ depends_on:
+ cqt-virtnet:
+ condition: service_started
+ entrypoint: ["geth", "--mainnet", "--port", "0", "--log.debug", "--syncmode", "full", "--datadir", "/root/.ethereum/covalent", "--replication.targets", "redis://username:@redis:6379/0?topic=replication", "--replica.result", "--replica.specimen"]
+ networks:
+ - cqt-net
+ expose:
+ - 8545
+ - 8546
+ - 30303
+ - 30303/udp
+
+ agent:
+ image: "ghcr.io/covalenthq/bsp-agent:latest"
+ container_name: bsp-agent
+ restart: on-failure
+ depends_on:
+ cqt-virtnet:
+ condition: service_started
+ entrypoint: >
+ /bin/bash -l -c "
+ echo Waiting for proof-chain to be deployed...;
+ while ! nc -v -l -k -p 8008;
+ do
+ sleep 1;
+ done;
+ echo proof-chain contracts deployed!;
+ ./bsp-agent --redis-url=redis://username:@redis:6379/0?topic=replication#replicate --avro-codec-path=./codec/block-ethereum.avsc --binary-file-path=./bin/block-ethereum/ --replica-bucket=covalenthq-geth-block-specimen --segment-length=10 --proof-chain-address=0xEa2ff902dbeEECcc828757B881b343F9316752e5 --consumer-timeout=6000;
+ exit 0;"
+ environment:
+ - ETH_PRIVATE_KEY=${PRIVATE_KEY}
+ - ETH_RPC_URL=${RPC_URL}
+ - BLOCKCHAIN=${BLOCKCHAIN}
+ networks:
+ - cqt-net
+ ports:
+ - "8080:8080"
+
+networks:
+ cqt-net:
diff --git a/docker-compose-testnet.yml b/docker-compose-testnet.yml
new file mode 100644
index 000000000..189db0a59
--- /dev/null
+++ b/docker-compose-testnet.yml
@@ -0,0 +1,75 @@
+version: '3'
+
+services:
+ redis:
+ image: redis:alpine
+ container_name: redis-srv
+ restart: always
+ expose:
+ - 6379
+ environment:
+ - REDIS_REPLICATION_MODE=master
+ networks:
+ - cqt-net
+ entrypoint: redis-server #/usr/local/etc/redis/redis.conf
+ ports:
+ - "6379:6379"
+ volumes:
+ - ./data/redis:/data
+
+ redis-commander:
+ image: rediscommander/redis-commander:latest
+ container_name: redis-commander-web
+ hostname: redis-commander
+ restart: always
+ depends_on:
+ - redis
+ environment:
+ - REDIS_HOSTS=local:redis:6379
+ networks:
+ - cqt-net
+ ports:
+ - "8081:8081"
+
+ geth:
+ image: "ghcr.io/covalenthq/bsp-geth:latest"
+ container_name: bsp-geth
+ restart: always
+ depends_on:
+ - redis
+ entrypoint: ["geth", "--mainnet", "--port", "0", "--log.debug", "--syncmode", "full", "--http", "--datadir", "/data/.ethereum/covalent", "--allow-insecure-unlock", "--preload", "/scripts/geth-setup.js", "--replication.targets", "redis://username:@redis:6379/0?topic=replication", "--replica.result", "--replica.specimen"]
+ networks:
+ - cqt-net
+ expose:
+ - 8545
+ - 8546
+ - 30303
+ - 30303/udp
+ volumes:
+ - ./scripts/:/scripts
+ - ./data/.ethereum/:/data/.ethereum/
+
+ agent:
+ image: "ghcr.io/covalenthq/bsp-agent:latest"
+ container_name: bsp-agent
+ restart: always
+ depends_on:
+ - geth
+ volumes:
+ - /Users/$USER/.config/gcloud:/app/gcloud
+ - ./data/bin/:/app/bin
+ entrypoint: >
+ /bin/bash -l -c "
+ ./bsp-agent --redis-url=redis://username:@redis:6379/0?topic=replication#replicate --avro-codec-path=./codec/block-ethereum.avsc --binary-file-path=./bin/block-ethereum/ --replica-bucket=covalenthq-geth-block-specimen --segment-length=1000 --proof-chain-address=0x67688076Da389cd1EcD90C4573d1D0e6BA04AC6a --consumer-timeout=6000 --gcp-svc-account=./gcloud/bsp-2.json;
+ exit 0;"
+ environment:
+ - ETH_PRIVATE_KEY=${PRIVATE_KEY}
+ - ETH_RPC_URL=${RPC_URL}
+ - BLOCKCHAIN=${BLOCKCHAIN}
+ networks:
+ - cqt-net
+ ports:
+ - "8080:8080"
+
+networks:
+ cqt-net:
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
new file mode 100644
index 000000000..8bde1b91f
--- /dev/null
+++ b/docs/CONTRIBUTING.md
@@ -0,0 +1,137 @@
+# Contributing
+
+* [Reporting Bugs](#bugs)
+* [General Procedure](#general_procedure)
+ * [Development Procedure](#dev_procedure)
+ * [Dependencies](#dependencies)
+ * [Testing](#testing)
+ * [Branching Model and Release](#braching_model_and_release)
+ * [PR Targeting](#pr_targeting)
+ * [Pull Requests](#pull_requests)
+ * [Process for reviewing PRs](#reviewing_prs)
+ * [Pull Merge Procedure](#pull_merge_procedure)
+
+
+## Reporting Bugs
+
+Please file bugs in the [GitHub Issue
+Tracker](https://github.com/covalenthq/bsp-geth). Include at
+least the following:
+
+ - What happened
+ - What did you expect to happen instead of what *did* happen, if it's
+ not crazy obvious
+ - What operating system, operating system version and version of
+ go-ethereum you are running
+ - Console log entries, where possible and relevant
+
+If you're not sure whether something is relevant, erring on the side of
+too much information will never be a cause for concern.
+
+
+## General Procedure
+
+Contributing to this repo can mean many things such as participating in discussion or proposing code changes. To ensure a smooth workflow for all contributors, the following general procedure for contributing has been established:
+
+1. Either [open](https://github.com/covalenthq/bsp-geth/issues/new/choose)
+ or [find](https://github.com/covalenthq/bsp-geth/issues) an issue you have identified and would like to contribute to
+ resolving.
+
+2. Participate in thoughtful discussion on that issue.
+
+3. If you would like to contribute:
+ 1. If the issue is a proposal, ensure that the proposal has been accepted by the Covalent team.
+ 2. Ensure that nobody else has already begun working on the same issue. If someone already has, please make sure to contact the individual to collaborate.
+ 3. If nobody has been assigned the issue and you would like to work on it, make a comment on the issue to inform the
+ community of your intentions to begin work. Ideally, wait for confirmation that no one has started it. However,
+ if you are eager and do not get a prompt response, feel free to dive on in!
+ 4. Follow standard Github best practices:
+ 1. Fork the repo
+ 2. Branch from the HEAD of `main`(For core developers working within the go-ethereum repo, to ensure a clear ownership of branches, branches must be named with the convention `{moniker}/{issue#}-branch-name`).
+ 3. Make commits
+ 4. Submit a PR to `main`
+ 5. Be sure to submit the PR in `Draft` mode. Submit your PR early, even if it's incomplete as this indicates to the community you're working on something and allows them to provide comments early in the development process.
+ 6. When the code is complete it can be marked `Ready for Review`.
+ 7. Be sure to include a relevant change log entry in the `Unreleased` section of `CHANGELOG.md` (see file for log
+ format).
+ 8. Please make sure to run `gofmt` before every commit - the easiest way to do this is having your editor run it for you upon saving a file. Additionally, please ensure that your code is lint compliant by running `make lint` . There are CI tests built into the bsp-geth repository and all PR’s will require that these tests pass before they are able to be merged.
+
+**Note**: for very small or blatantly obvious problems (such as typos), it is not required to open an issue to submit a
+PR, but be aware that for more complex problems/features, if a PR is opened before an adequate design discussion has
+taken place in a github issue, that PR runs a high likelihood of being rejected.
+
+Looking for a good place to start contributing? How about checking out
+some [good first issues](https://github.com/covalenthq/bsp-geth/issues).
+
+### Development Procedure
+
+1. The latest state of development is on `main`.
+2. `main` must never
+ fail `make lint, make geth, make all `
+3. No `--force` onto `main` (except when reverting a broken commit, which should seldom happen).
+4. Create your feature branch from `main` either on `github.com/covalenthq/bsp-geth`, or your fork (
+ using `git remote add origin`).
+5. Before submitting a pull request, begin `git rebase` on top of `main`.
+6. Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting)
+ guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+7. Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary)
+ guidelines.
+8. Pull requests need to be based on and opened against the `main` branch.
+9. Commit messages should be prefixed with the package(s) they modify.
+ * E.g. "eth, rpc: make trace configs optional"
+
+
+### Dependencies
+
+We use [Go Modules](https://github.com/golang/go/wiki/Modules) to manage dependency versions.
+
+The main branch of every bsp-geth repository should just build with `go get`, which means they should be kept up-to-date
+with their dependencies, so we can get away with telling people they can just `go get` our software. Since some dependencies are not under our control, a third party may break our build, in which case we can fall back
+on `go mod tidy -v`.
+
+### Testing
+
+Covalent uses [GitHub Actions](https://github.com/features/actions) for automated [integration testing](https://github.com/covalenthq/bsp-geth/actions).
+
+### Branching Model and Release
+
+User-facing repos should adhere to the [trunk based development branching model](https://trunkbaseddevelopment.com/).
+
+Libraries need not follow the model strictly, but would be wise to.
+
+bsp-geth utilizes [semantic versioning](https://semver.org/).
+
+### PR Targeting
+
+Ensure that you base and target your PR on the `main` branch.
+
+All feature additions should be targeted against `main`. Bug fixes for an outstanding release candidate should be
+targeted against the release candidate branch.
+
+### Pull Requests
+
+To accommodate the review process, we suggest that PRs are categorically broken up. Ideally each PR addresses only a
+single issue. Additionally, as much as possible code refactoring and cleanup should be submitted as separate PRs from
+bug fixes/feature-additions.
+
+### Process for reviewing PRs
+
+All PRs require at least 1 Review before merge. When reviewing PRs, please use the following review explanations:
+
+1. `LGTM` without an explicit approval means that the changes look good, but you haven't pulled down the code, run tests
+ locally and thoroughly reviewed it.
+2. `Approval` through the GH UI means that you understand the code, documentation/spec is updated in the right places,
+ you have pulled down and tested the code locally. In addition:
+ * You must think through whether any added code could be partially combined (DRYed) with existing code.
+ * You must think through any potential security issues or incentive-compatibility flaws introduced by the changes.
+ * Naming convention must be consistent with the rest of the codebase.
+ * Code must live in a reasonable location, considering dependency structures (e.g. not importing testing modules in
+ production code, or including example code modules in production code).
+ * If you approve of the PR, you are responsible for fixing any of the issues mentioned here.
+3. If you are only making "surface level" reviews, submit any notes as `Comments` without adding a review.
+
+### Pull Merge Procedure
+
+1. Ensure pull branch is rebased on `main`.
+2. Ensure that all CI tests pass.
+3. Squash merge pull request.
\ No newline at end of file
diff --git a/docs/arch.jpg b/docs/arch.jpg
new file mode 100644
index 000000000..5c901ebf5
Binary files /dev/null and b/docs/arch.jpg differ
diff --git a/docs/covalent.jpg b/docs/covalent.jpg
new file mode 100644
index 000000000..54f33bdc7
Binary files /dev/null and b/docs/covalent.jpg differ
diff --git a/docs/extract.png b/docs/extract.png
new file mode 100644
index 000000000..5fc352d54
Binary files /dev/null and b/docs/extract.png differ
diff --git a/docs/postmortems/2021-08-22-split-postmortem.md b/docs/postmortems/2021-08-22-split-postmortem.md
index 2e5c41c76..0986f00b6 100644
--- a/docs/postmortems/2021-08-22-split-postmortem.md
+++ b/docs/postmortems/2021-08-22-split-postmortem.md
@@ -56,13 +56,13 @@ On the evening of 17th, we discussed options on how to handle it. We made a stat
It was decided that in this specific instance, it would be possible to make a public announcement and a patch release:
- The fix can be made pretty 'generically', e.g. always copying data on input to precompiles.
-- The flaw is pretty difficult to find, given a generic fix in the call. The attacker needs to figure out that it concerns the precompiles, specifically the datacopy, and that it concerns the `RETURNDATA` buffer rather than the regular memory, and lastly the special circumstances to trigger it (overlapping but shifted input/output).
+- The flaw is pretty difficult to find, given a generic fix in the call. The attacker needs to figure out that it concerns the precompiles, specifically the datcopy, and that it concerns the `RETURNDATA` buffer rather than the regular memory, and lastly the special circumstances to trigger it (overlapping but shifted input/output).
Since we had merged the removal of `ETH65`, if the entire network were to upgrade, then nodes which have not yet implemented `ETH66` would be cut off from the network. After further discussions, we decided to:
- Announce an upcoming security release on Tuesday (August 24th), via Twitter and official channels, plus reach out to downstream projects.
- Temporarily revert the `ETH65`-removal.
-- Place the fix into the PR optimizing the jumpdest analysis [23381](https://github.com/ethereum/go-ethereum/pull/23381).
+- Place the fix into the PR optimizing the jumpdest analysis [233381](https://github.com/ethereum/go-ethereum/pull/23381).
- After 4-8 weeks, release details about the vulnerability.
diff --git a/docs/value.png b/docs/value.png
new file mode 100644
index 000000000..bd8f6e09f
Binary files /dev/null and b/docs/value.png differ
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 57f5a5083..1ece3ebef 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -20,6 +20,7 @@ import (
"context"
"errors"
"math/big"
+ "sync/atomic"
"time"
"github.com/ethereum/go-ethereum"
@@ -40,6 +41,7 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
@@ -468,3 +470,18 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
}
+
+// SetHistoricalBlocksSynced returns a bool for BSP replica config (Historical mode :0 , Live mode: 1)
+func (b *EthAPIBackend) SetHistoricalBlocksSynced() bool {
+ if b.eth.Synced() {
+ atomic.StoreUint32(b.eth.blockchain.ReplicaConfig.HistoricalBlocksSynced, 1)
+ log.Info("Fully Synced, BSP running in live sync mode", "BSP Mode Config: ", atomic.LoadUint32(b.eth.blockchain.ReplicaConfig.HistoricalBlocksSynced))
+ return true
+ } else {
+ // log.Error("Not accepting new transactions, BSP running in historical sync mode", "BSP Mode Config: ", atomic.LoadUint32(b.eth.blockchain.ReplicaConfig.HistoricalBlocksSynced))
+ // return false
+ atomic.StoreUint32(b.eth.blockchain.ReplicaConfig.HistoricalBlocksSynced, 1)
+ log.Info("Fully Synced, BSP running in live sync mode", "BSP Mode Config: ", atomic.LoadUint32(b.eth.blockchain.ReplicaConfig.HistoricalBlocksSynced))
+ return true
+ }
+}
diff --git a/eth/backend.go b/eth/backend.go
index 6d1b6bae9..187d0d11e 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -100,7 +100,9 @@ type Ethereum struct {
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
- shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
+ shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
+ blockReplicators []*core.ChainReplicator
+ ReplicaConfig *core.ReplicaConfig
}
// New creates a new Ethereum object (including the initialisation of the common Ethereum object),
@@ -161,16 +163,31 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Assemble the Ethereum object.
eth := &Ethereum{
- config: config,
- chainDb: chainDb,
- eventMux: stack.EventMux(),
- accountManager: stack.AccountManager(),
- engine: engine,
- networkID: networkID,
- gasPrice: config.Miner.GasPrice,
- p2pServer: stack.Server(),
- discmix: enode.NewFairMix(0),
- shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
+ config: config,
+ chainDb: chainDb,
+ eventMux: stack.EventMux(),
+ accountManager: stack.AccountManager(),
+ engine: engine,
+ networkID: networkID,
+ gasPrice: config.Miner.GasPrice,
+ p2pServer: stack.Server(),
+ discmix: enode.NewFairMix(0),
+ shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
+ blockReplicators: make([]*core.ChainReplicator, 0),
+ ReplicaConfig: &core.ReplicaConfig{
+ EnableSpecimen: config.ReplicaEnableSpecimen,
+ EnableResult: config.ReplicaEnableResult,
+ EnableBlob: config.ReplicaEnableBlob,
+ HistoricalBlocksSynced: new(uint32), // Always set 0 for historical mode at start
+ },
+ }
+ for _, targets := range config.BlockReplicationTargets {
+ replicator, err := CreateReplicator(targets)
+ if err != nil {
+ return nil, err
+ }
+ log.Info("Block replication started", "targets", targets, "network ID", config.NetworkId, "export block-specimen", eth.ReplicaConfig.EnableSpecimen, "export block-result", eth.ReplicaConfig.EnableResult, "export blob-specimen", eth.ReplicaConfig.EnableBlob)
+ eth.blockReplicators = append(eth.blockReplicators, replicator)
}
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
var dbVer = ""
@@ -238,6 +255,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
ExportFileName: config.LogExportCheckpoints,
HashScheme: scheme == rawdb.HashScheme,
}
+ eth.blockchain.SetBlockReplicaExports(eth.ReplicaConfig)
+ for _, bRRepl := range eth.blockReplicators {
+ bRRepl.Start(eth.blockchain, eth.ReplicaConfig)
+ }
+ if config.BlobPool.Datadir != "" {
+ config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir)
+ }
chainView := eth.newChainView(eth.blockchain.CurrentBlock())
historyCutoff, _ := eth.blockchain.HistoryPruningCutoff()
var finalBlock uint64
@@ -515,6 +539,9 @@ func (s *Ethereum) Stop() error {
s.filterMaps.Stop()
s.txPool.Close()
s.blockchain.Stop()
+ for _, repl := range s.blockReplicators {
+ repl.Stop()
+ }
s.engine.Close()
// Clean shutdown marker as the last thing before closing db
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 5e1982413..2e0eff40c 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -162,6 +162,14 @@ type Config struct {
// OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"`
+
+ // List of URIs to connect replication providers to
+ BlockReplicationTargets []string `toml:",omitempty"`
+
+ // Bools that make explicit types being exported
+ ReplicaEnableResult bool
+ ReplicaEnableSpecimen bool
+ ReplicaEnableBlob bool
}
// CreateConsensusEngine creates a consensus engine for the given chain config.
diff --git a/eth/redis_queue_replicator.go b/eth/redis_queue_replicator.go
new file mode 100644
index 000000000..cfb97ba95
--- /dev/null
+++ b/eth/redis_queue_replicator.go
@@ -0,0 +1,69 @@
+package eth
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/url"
+
+ "github.com/ethereum/go-ethereum/core"
+
+ "github.com/go-redis/redis/v7"
+ "github.com/golang/snappy"
+)
+
+type RedisQueueReplicator struct {
+ rdb *redis.Client
+ qKey string
+ description string
+ compBuf []byte
+}
+
+const redisQueueReplicatorCompBufSize = 20 * 1024 * 1024
+
+func NewRedisQueueReplicator(rdbURL *url.URL) (*core.ChainReplicator, error) {
+ q := rdbURL.Query()
+ topic := q.Get("topic")
+ if len(topic) == 0 {
+ return nil, errors.New("redis replication target requires 'topic' query-param")
+ }
+ q.Del("topic")
+ rdbURL.RawQuery = q.Encode()
+
+ rdbOpts, err := redis.ParseURL(rdbURL.String())
+ if err != nil {
+ return nil, err
+ }
+
+ backend := &RedisQueueReplicator{
+ rdb: redis.NewClient(rdbOpts),
+ qKey: topic,
+ description: fmt.Sprintf("Redis(addr=%s,type=stream,key=%s)", rdbOpts.Addr, topic),
+ compBuf: make([]byte, 0, redisQueueReplicatorCompBufSize),
+ }
+
+ return core.NewChainReplicator(backend), nil
+}
+
+func (r *RedisQueueReplicator) String() string {
+ return r.description
+}
+
+func (r *RedisQueueReplicator) Process(ctx context.Context, events []*core.BlockReplicationEvent) (err error) {
+ pipe := r.rdb.WithContext(ctx).Pipeline()
+
+ for _, event := range events {
+ encodedData := snappy.Encode(nil, event.Data)
+ pipe.XAdd(&redis.XAddArgs{
+ Stream: r.qKey,
+ MaxLenApprox: 500000,
+ Values: map[string]interface{}{
+ "hash": event.Hash,
+ "data": encodedData,
+ },
+ })
+ }
+
+ _, err = pipe.Exec()
+ return
+}
diff --git a/eth/replicators.go b/eth/replicators.go
new file mode 100644
index 000000000..370d57a2a
--- /dev/null
+++ b/eth/replicators.go
@@ -0,0 +1,24 @@
+package eth
+
+import (
+ "fmt"
+ "net/url"
+
+ "github.com/ethereum/go-ethereum/core"
+)
+
+func CreateReplicator(target string) (*core.ChainReplicator, error) {
+ targetURL, err := url.Parse(target)
+ if err != nil {
+ return nil, err
+ }
+
+ switch targetURL.Scheme {
+ case "redis", "rediss":
+ return NewRedisQueueReplicator(targetURL)
+ case "file":
+ return NewRLPFileSetReplicator(targetURL)
+ default:
+ return nil, fmt.Errorf("unknown replication-target URI scheme '%s'", targetURL.Scheme)
+ }
+}
diff --git a/eth/rlp_fileset_replicator.go b/eth/rlp_fileset_replicator.go
new file mode 100644
index 000000000..faaf2849c
--- /dev/null
+++ b/eth/rlp_fileset_replicator.go
@@ -0,0 +1,116 @@
+package eth
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "os"
+ "path"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/rlp"
+
+ "github.com/golang/snappy"
+)
+
+type RLPFileSetReplicator struct {
+ filesetBasePath string
+ nodeStartID uint64
+ chunkSeq uint64
+ chunkFile *os.File
+ chunkBytesWritten uint64
+ description string
+ compBuf []byte
+}
+
+const rlpFileReplicationChunkSizeLimit = 5 * 1024 * 1024
+const rlpFileReplicationCompBufSize = 20 * 1024 * 1024
+
+func NewRLPFileSetReplicator(filesetBaseURI *url.URL) (*core.ChainReplicator, error) {
+ filesetBasePath := filesetBaseURI.Path
+
+ err := os.MkdirAll(filesetBasePath, 0755)
+ if err != nil {
+ return nil, err
+ }
+
+ nodeStartID := uint64(time.Now().Unix())
+
+ backend := &RLPFileSetReplicator{
+ filesetBasePath: filesetBasePath,
+ nodeStartID: nodeStartID,
+ description: fmt.Sprintf("RLPFileSet(path=%s)", filesetBasePath),
+ compBuf: make([]byte, 0, rlpFileReplicationCompBufSize),
+ }
+
+ err = backend.OpenNextChunk()
+ if err != nil {
+ return nil, err
+ }
+
+ return core.NewChainReplicator(backend), nil
+}
+
+func (r *RLPFileSetReplicator) OpenNextChunk() error {
+ if r.chunkFile != nil {
+ r.chunkFile.Close()
+ r.chunkSeq++
+ }
+
+ chunkFileName := fmt.Sprintf("sess-%08x-chunk-%08x.rlp", r.nodeStartID, r.chunkSeq)
+ chunkFilePath := path.Join(r.filesetBasePath, chunkFileName)
+
+ f, err := os.Create(chunkFilePath)
+ if err != nil {
+ return err
+ }
+
+ r.chunkFile = f
+ r.chunkBytesWritten = 0
+
+ return nil
+}
+
+func (r *RLPFileSetReplicator) String() string {
+ return r.description
+}
+
+func (r *RLPFileSetReplicator) Process(ctx context.Context, events []*core.BlockReplicationEvent) (err error) {
+ var (
+ rlpData []byte
+ compRlpData []byte
+ bytesWrittenForStep uint64
+ bytesWrittenForEvent int
+ )
+
+ for _, event := range events {
+ rlpData, err = rlp.EncodeToBytes([]interface{}{
+ event.Hash,
+ event.Data,
+ })
+ if err != nil {
+ return
+ }
+
+ compRlpData = snappy.Encode(r.compBuf, rlpData)
+
+ bytesWrittenForEvent, err = r.chunkFile.Write(compRlpData)
+ if err != nil {
+ return
+ }
+ bytesWrittenForStep += uint64(bytesWrittenForEvent)
+ }
+
+ err = r.chunkFile.Sync()
+ if err != nil {
+ return
+ }
+
+ r.chunkBytesWritten += bytesWrittenForStep
+ if r.chunkBytesWritten >= rlpFileReplicationChunkSizeLimit {
+ err = r.OpenNextChunk()
+ }
+
+ return
+}
diff --git a/go.mod b/go.mod
index 968268593..f1e5c16fe 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/ethereum/go-ethereum
-go 1.23.0
+go 1.24.0
require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
@@ -29,6 +29,7 @@ require (
github.com/fjl/gencodec v0.1.0
github.com/fsnotify/fsnotify v1.6.0
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
+ github.com/go-redis/redis/v7 v7.4.1
github.com/gofrs/flock v0.8.1
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
diff --git a/go.sum b/go.sum
index 1ac65bc01..cc95f722a 100644
--- a/go.sum
+++ b/go.sum
@@ -205,6 +205,8 @@ 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-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
+github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -406,9 +408,11 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
@@ -596,6 +600,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -663,6 +668,7 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
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-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go
index 0a157dce7..268f94022 100644
--- a/internal/ethapi/api_test.go
+++ b/internal/ethapi/api_test.go
@@ -636,6 +636,8 @@ func (b testBackend) HistoryPruningCutoff() uint64 {
return bn
}
+func (b testBackend) SetHistoricalBlocksSynced() bool { return true }
+
func TestEstimateGas(t *testing.T) {
t.Parallel()
// Initialize test accounts
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 49c3a3756..2bb72c717 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -98,6 +98,8 @@ type Backend interface {
CurrentView() *filtermaps.ChainView
NewMatcherBackend() filtermaps.MatcherBackend
+
+ SetHistoricalBlocksSynced() bool
}
func GetAPIs(apiBackend Backend) []rpc.API {
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
index 9b86e452a..2eaced769 100644
--- a/internal/ethapi/transaction_args_test.go
+++ b/internal/ethapi/transaction_args_test.go
@@ -408,3 +408,6 @@ func (b *backendMock) CurrentView() *filtermaps.ChainView { return nil
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }
+func (b *backendMock) SetHistoricalBlocksSynced() bool {
+ return true
+}
diff --git a/miner/worker.go b/miner/worker.go
index d80cb8913..9278e0c25 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -43,6 +43,8 @@ var (
errBlockInterruptedByTimeout = errors.New("timeout while building block")
)
+var enableBlobTxSidecar bool
+
// environment is the worker's current environment and holds all
// information of the sealing block generation.
type environment struct {
@@ -99,6 +101,9 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo
if err != nil {
return &newPayloadResult{err: err}
}
+ if miner.chain.ReplicaConfig.EnableBlob {
+ enableBlobTxSidecar = true
+ }
if !params.noTxs {
interrupt := new(atomic.Int32)
timer := time.AfterFunc(miner.config.Recommit, func() {
@@ -139,6 +144,21 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo
reqHash := types.CalcRequestsHash(requests)
work.header.RequestsHash = &reqHash
}
+ if enableBlobTxSidecar {
+ work.sidecars = make([]*types.BlobTxSidecar, len(work.sidecars))
+ copy(work.sidecars, work.sidecars)
+ types.BlobTxSidecarChan = make(chan *types.BlobTxSidecarData, 100)
+ go func() {
+ for sidecar := range work.sidecars {
+ types.BlobTxSidecarChan <- &types.BlobTxSidecarData{
+ Blobs: work.sidecars[sidecar],
+ BlockNumber: work.header.Number,
+ }
+ }
+ log.Info("Closing Chain Sync BlobTxSidecar Channel For", "Block Number:", work.header.Number.Uint64(), "Length:", len(types.BlobTxSidecarChan))
+ close(types.BlobTxSidecarChan)
+ }()
+ }
block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts)
if err != nil {
diff --git a/params/version.go b/params/version.go
new file mode 100644
index 000000000..59f996141
--- /dev/null
+++ b/params/version.go
@@ -0,0 +1,78 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package params
+
+import (
+ "fmt"
+)
+
+const (
+ VersionMajor = 1 // Major version component of the current release
+ VersionMinor = 15 // Minor version component of the current release
+ VersionPatch = 10 // Patch version component of the current release
+ VersionMeta = "stable" // Version metadata to append to the version string
+)
+
+const (
+ BspVersionMajor = 2 // Major version component of the current release
+ BspVersionMinor = 2 // Minor version component of the current release
+ BspVersionPatch = 0 // Patch version component of the current release
+)
+
+// Version holds the textual version string.
+var Version = func() string {
+ return fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch)
+}()
+
+// BspVersion holds the textual version string.
+var BspVersion = func() string {
+ return fmt.Sprintf("%d.%d.%d-%v", BspVersionMajor, BspVersionMinor, BspVersionPatch, "bsp")
+}()
+
+// VersionWithMeta holds the textual version string including the metadata.
+var VersionWithMeta = func() string {
+ v := Version
+ if VersionMeta != "" {
+ v += "-" + VersionMeta + "-" + BspVersion
+ }
+ return v
+}()
+
+// ArchiveVersion holds the textual version string used for Geth archives. e.g.
+// "1.8.11-dea1ce05" for stable releases, or "1.8.13-unstable-21c059b6" for unstable
+// releases.
+func ArchiveVersion(gitCommit string) string {
+ vsn := Version
+ if VersionMeta != "stable" {
+ vsn += "-" + VersionMeta
+ }
+ if len(gitCommit) >= 8 {
+ vsn += "-" + gitCommit[:8]
+ }
+ return vsn
+}
+
+func VersionWithCommit(gitCommit, gitDate string) string {
+ vsn := VersionWithMeta
+ if len(gitCommit) >= 8 {
+ vsn += "-" + gitCommit[:8]
+ }
+ if (VersionMeta != "stable") && (gitDate != "") {
+ vsn += "-" + gitDate
+ }
+ return vsn
+}