diff --git a/.github/matic-cli-config.yml b/.github/matic-cli-config.yml
index 7233eb0497..8c31e17dc6 100644
--- a/.github/matic-cli-config.yml
+++ b/.github/matic-cli-config.yml
@@ -10,8 +10,12 @@ blockNumber:
- '0'
blockTime:
- '2'
-numOfValidators: 3
-numOfNonValidators: 0
+numOfBorValidators: 3
+numOfBorSentries: 0
+numOfBorArchiveNodes: 0
+numOfErigonValidators: 0
+numOfErigonSentries: 0
+numOfErigonArchiveNodes: 0
ethURL: http://ganache:9545
ethHostUser: ubuntu
devnetType: docker
@@ -19,4 +23,3 @@ borDockerBuildContext: "../../bor"
heimdallDockerBuildContext: "https://github.com/maticnetwork/heimdall.git#develop"
sprintSizeBlockNumber:
- '0'
-numOfArchiveNodes: 0
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index c467800e58..7b58af0bc0 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -23,6 +23,8 @@ In case this PR includes changes that must be applied only to a subset of nodes,
- [ ] I have added at least 2 reviewer or the whole pos-v1 team
- [ ] I have added sufficient documentation in code
- [ ] I will be resolving comments - if any - by pushing each fix in a separate commit and linking the commit hash in the comment reply
+- [ ] Created a task in Jira and informed the team for implementation in Erigon client (if applicable)
+- [ ] Includes RPC methods changes, and the Notion documentation has been updated
# Cross repository changes
diff --git a/.github/workflows/packager.yml b/.github/workflows/packager.yml
index aa14a3ce22..f2aef42485 100644
--- a/.github/workflows/packager.yml
+++ b/.github/workflows/packager.yml
@@ -47,7 +47,7 @@ jobs:
run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: amd64
-
+
- name: Running package build
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
@@ -61,116 +61,6 @@ jobs:
- name: Copying systemd file
run: cp -rp packaging/templates/systemd/bor.service packaging/deb/bor/lib/systemd/system/bor.service
- - name: Prepping ${{ env.NETWORK }} ${{ env.NODE }} bootnode for ${{ env.ARCH }}
- run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: any
- - name: Putting toml for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/testnet-v4/sentry/sentry/bor/config.toml packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/var/lib/bor/
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mumbai
- - name: Copying the preinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/preinst packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/preinst
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: any
- - name: Copying the postinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postinst.profile packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postinst
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: any
- - name: Copying the prerm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/prerm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/prerm
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: any
- - name: Copying the postrm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postrm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postrm
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: any
- - name: Copying systemd file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/systemd/bor_bootnode.service packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/lib/systemd/system/
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: any
- - name: Copying profile control file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/control.profile.amd64 packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/control
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mumbai
- - name: Running package build for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mumbai
-
- - name: Prepping ${{ env.NETWORK }} ${{ env.NODE }} bootnode for ${{ env.ARCH }}
- run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Putting toml for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/var/lib/bor/
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the preinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/preinst packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/preinst
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the postinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postinst.profile packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postinst
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the prerm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/prerm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/prerm
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the postrm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postrm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postrm
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying systemd file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/systemd/bor_bootnode.service packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/lib/systemd/system/
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying profile control file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/control.profile.amd64 packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/control
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
- - name: Running package build for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: amd64
- NODE: bootnode
- NETWORK: mainnet
-
- name: Prepping ${{ env.NETWORK }} ${{ env.NODE }} node for ${{ env.ARCH }}
run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
@@ -476,7 +366,7 @@ jobs:
ARCH: amd64
NODE: archive
NETWORK: mainnet
- - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
+ - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: amd64
@@ -526,116 +416,6 @@ jobs:
- name: Updating the control file to use with the arm64 profile
run: cp -rp packaging/templates/package_scripts/control.profile.arm64 packaging/deb/bor/DEBIAN/control
- - name: Prepping ${{ env.NETWORK }} ${{ env.NODE }} bootnode for ${{ env.ARCH }}
- run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Putting toml for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/testnet-v4/sentry/sentry/bor/config.toml packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/var/lib/bor/
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Copying the preinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/preinst packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/preinst
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Copying the postinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postinst.profile packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postinst
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Copying the prerm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/prerm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/prerm
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Copying the postrm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postrm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postrm
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Copying systemd file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/systemd/bor_bootnode.service packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/lib/systemd/system/
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Copying profile control file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/control.profile.amd64 packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/control
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
- - name: Running package build for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mumbai
-
- - name: Prepping ${{ env.NETWORK }} ${{ env.NODE }} bootnode for ${{ env.ARCH }}
- run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Putting toml for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/var/lib/bor/
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the preinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/preinst packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/preinst
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the postinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postinst.profile packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postinst
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the prerm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/prerm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/prerm
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying the postrm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/postrm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postrm
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying systemd file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/systemd/bor_bootnode.service packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/lib/systemd/system/
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Copying profile control file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: cp -rp packaging/templates/package_scripts/control.profile.amd64 packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/control
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
- - name: Running package build for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
- run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
- env:
- ARCH: arm64
- NODE: bootnode
- NETWORK: mainnet
-
- name: Setting up bor for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
@@ -739,7 +519,7 @@ jobs:
ARCH: arm64
NODE: sentry
NETWORK: mainnet
- - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
+ - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: arm64
@@ -849,13 +629,13 @@ jobs:
ARCH: arm64
NODE: validator
NETWORK: mainnet
- - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
+ - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: arm64
NODE: validator
NETWORK: mainnet
-
+
- name: Updating the control file to use with the arm64 profile
run: cp -rp packaging/templates/package_scripts/control.profile.arm64 packaging/deb/bor/DEBIAN/control
@@ -895,7 +675,7 @@ jobs:
ARCH: arm64
NODE: archive
NETWORK: mumbai
- - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
+ - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: arm64
@@ -938,7 +718,7 @@ jobs:
ARCH: arm64
NODE: archive
NETWORK: mainnet
- - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
+ - name: Building bor ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }} profile
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: arm64
diff --git a/.github/workflows/security-ci.yml b/.github/workflows/security-ci.yml
index 61f4d7ddbb..1d88a8b91a 100644
--- a/.github/workflows/security-ci.yml
+++ b/.github/workflows/security-ci.yml
@@ -49,7 +49,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Running govulncheck
- uses: Templum/govulncheck-action@v0.0.8
+ uses: Templum/govulncheck-action@v0.10.1
continue-on-error: true
env:
DEBUG: "true"
diff --git a/builder/files/config.toml b/builder/files/config.toml
index f2ed492279..2e758bc8ee 100644
--- a/builder/files/config.toml
+++ b/builder/files/config.toml
@@ -71,6 +71,7 @@ syncmode = "full"
# etherbase = "VALIDATOR ADDRESS"
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
# [jsonrpc]
@@ -128,7 +129,7 @@ syncmode = "full"
metrics = true
# expensive = false
# prometheus-addr = "127.0.0.1:7071"
- # opencollector-endpoint = "127.0.0.1:4317"
+ # opencollector-endpoint = ""
# [telemetry.influx]
# influxdb = false
# endpoint = ""
@@ -171,6 +172,10 @@ syncmode = "full"
# period = 0
# gaslimit = 11500000
+# [parallelevm]
+ # enable = true
+ # procs = 8
+
# [pprof]
# pprof = false
# port = 6060
diff --git a/builder/files/genesis-testnet-v4.json b/builder/files/genesis-testnet-v4.json
index fe066411a3..3fc61e2f64 100644
--- a/builder/files/genesis-testnet-v4.json
+++ b/builder/files/genesis-testnet-v4.json
@@ -16,6 +16,11 @@
"bor": {
"jaipurBlock": 22770000,
"delhiBlock": 29638656,
+ "parallelUniverseBlock": 0,
+ "indoreBlock": 36877056,
+ "stateSyncConfirmationDelay": {
+ "36877056": 128
+ },
"period": {
"0": 2,
"25275000": 5,
diff --git a/consensus/bor/api/caller.go b/consensus/bor/api/caller.go
index d5fe259c97..49c06b3f73 100644
--- a/consensus/bor/api/caller.go
+++ b/consensus/bor/api/caller.go
@@ -4,6 +4,7 @@ import (
"context"
"github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
)
@@ -11,4 +12,5 @@ import (
//go:generate mockgen -destination=./caller_mock.go -package=api . Caller
type Caller interface {
Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride) (hexutil.Bytes, error)
+ CallWithState(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *ethapi.StateOverride) (hexutil.Bytes, error)
}
diff --git a/consensus/bor/api/caller_mock.go b/consensus/bor/api/caller_mock.go
index 940c99d178..e734d6c899 100644
--- a/consensus/bor/api/caller_mock.go
+++ b/consensus/bor/api/caller_mock.go
@@ -9,6 +9,7 @@ import (
reflect "reflect"
hexutil "github.com/ethereum/go-ethereum/common/hexutil"
+ state "github.com/ethereum/go-ethereum/core/state"
ethapi "github.com/ethereum/go-ethereum/internal/ethapi"
rpc "github.com/ethereum/go-ethereum/rpc"
gomock "github.com/golang/mock/gomock"
@@ -51,3 +52,18 @@ func (mr *MockCallerMockRecorder) Call(arg0, arg1, arg2, arg3 interface{}) *gomo
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockCaller)(nil).Call), arg0, arg1, arg2, arg3)
}
+
+// CallWithState mocks base method.
+func (m *MockCaller) CallWithState(arg0 context.Context, arg1 ethapi.TransactionArgs, arg2 rpc.BlockNumberOrHash, arg3 *state.StateDB, arg4 *ethapi.StateOverride) (hexutil.Bytes, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CallWithState", arg0, arg1, arg2, arg3, arg4)
+ ret0, _ := ret[0].(hexutil.Bytes)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CallWithState indicates an expected call of CallWithState.
+func (mr *MockCallerMockRecorder) CallWithState(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallWithState", reflect.TypeOf((*MockCaller)(nil).CallWithState), arg0, arg1, arg2, arg3, arg4)
+}
diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go
index d63b60fa58..aa35dc2fbc 100644
--- a/consensus/bor/bor.go
+++ b/consensus/bor/bor.go
@@ -1179,22 +1179,42 @@ func (c *Bor) CommitStates(
fetchStart := time.Now()
number := header.Number.Uint64()
- _lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1)
- if err != nil {
- return nil, err
+ var (
+ lastStateIDBig *big.Int
+ from uint64
+ to time.Time
+ err error
+ )
+
+ if c.config.IsIndore(header.Number) {
+ // Fetch the LastStateId from contract via current state instance
+ lastStateIDBig, err = c.GenesisContractsClient.LastStateId(state.Copy(), number-1, header.ParentHash)
+ if err != nil {
+ return nil, err
+ }
+
+ stateSyncDelay := c.config.CalculateStateSyncDelay(number)
+ to = time.Unix(int64(header.Time-stateSyncDelay), 0)
+ } else {
+ lastStateIDBig, err = c.GenesisContractsClient.LastStateId(nil, number-1, header.ParentHash)
+ if err != nil {
+ return nil, err
+ }
+
+ to = time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.CalculateSprint(number)).Time), 0)
}
- to := time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.CalculateSprint(number)).Time), 0)
- lastStateID := _lastStateID.Uint64()
+ lastStateID := lastStateIDBig.Uint64()
+ from = lastStateID + 1
log.Info(
"Fetching state updates from Heimdall",
- "fromID", lastStateID+1,
+ "fromID", from,
"to", to.Format(time.RFC3339))
- eventRecords, err := c.HeimdallClient.StateSyncEvents(ctx, lastStateID+1, to.Unix())
+ eventRecords, err := c.HeimdallClient.StateSyncEvents(ctx, from, to.Unix())
if err != nil {
- log.Error("Error occurred when fetching state sync events", "stateID", lastStateID+1, "error", err)
+ log.Error("Error occurred when fetching state sync events", "fromID", from, "to", to.Unix(), "err", err)
}
if c.config.OverrideStateSyncRecords != nil {
@@ -1217,7 +1237,7 @@ func (c *Bor) CommitStates(
}
if err = validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil {
- log.Error("while validating event record", "block", number, "to", to, "stateID", lastStateID, "error", err.Error())
+ log.Error("while validating event record", "block", number, "to", to, "stateID", lastStateID+1, "error", err.Error())
break
}
diff --git a/consensus/bor/contract/client.go b/consensus/bor/contract/client.go
index 9e9e1392dd..fa8ad215f2 100644
--- a/consensus/bor/contract/client.go
+++ b/consensus/bor/contract/client.go
@@ -100,8 +100,8 @@ func (gc *GenesisContractsClient) CommitState(
return gasUsed, nil
}
-func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int, error) {
- blockNr := rpc.BlockNumber(snapshotNumber)
+func (gc *GenesisContractsClient) LastStateId(state *state.StateDB, number uint64, hash common.Hash) (*big.Int, error) {
+ blockNr := rpc.BlockNumber(number)
const method = "lastStateId"
@@ -116,11 +116,13 @@ func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int,
toAddress := common.HexToAddress(gc.StateReceiverContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
- result, err := gc.ethAPI.Call(context.Background(), ethapi.TransactionArgs{
+ // Do a call with state so that we can fetch the last state ID from a given (incoming)
+ // state instead of local(canonical) chain.
+ result, err := gc.ethAPI.CallWithState(context.Background(), ethapi.TransactionArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
- }, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil)
+ }, rpc.BlockNumberOrHash{BlockNumber: &blockNr, BlockHash: &hash}, state, nil)
if err != nil {
return nil, err
}
diff --git a/consensus/bor/genesis.go b/consensus/bor/genesis.go
index 33de53f9ba..9519b18847 100644
--- a/consensus/bor/genesis.go
+++ b/consensus/bor/genesis.go
@@ -3,6 +3,7 @@ package bor
import (
"math/big"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
"github.com/ethereum/go-ethereum/core/state"
@@ -12,5 +13,5 @@ import (
//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract
type GenesisContract interface {
CommitState(event *clerk.EventRecordWithTime, state *state.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error)
- LastStateId(snapshotNumber uint64) (*big.Int, error)
+ LastStateId(state *state.StateDB, number uint64, hash common.Hash) (*big.Int, error)
}
diff --git a/consensus/bor/genesis_contract_mock.go b/consensus/bor/genesis_contract_mock.go
index dfe9390509..0296cbe905 100644
--- a/consensus/bor/genesis_contract_mock.go
+++ b/consensus/bor/genesis_contract_mock.go
@@ -8,6 +8,7 @@ import (
big "math/big"
reflect "reflect"
+ common "github.com/ethereum/go-ethereum/common"
clerk "github.com/ethereum/go-ethereum/consensus/bor/clerk"
statefull "github.com/ethereum/go-ethereum/consensus/bor/statefull"
state "github.com/ethereum/go-ethereum/core/state"
@@ -54,16 +55,16 @@ func (mr *MockGenesisContractMockRecorder) CommitState(arg0, arg1, arg2, arg3 in
}
// LastStateId mocks base method.
-func (m *MockGenesisContract) LastStateId(arg0 uint64) (*big.Int, error) {
+func (m *MockGenesisContract) LastStateId(arg0 *state.StateDB, arg1 uint64, arg2 common.Hash) (*big.Int, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "LastStateId", arg0)
+ ret := m.ctrl.Call(m, "LastStateId", arg0, arg1, arg2)
ret0, _ := ret[0].(*big.Int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LastStateId indicates an expected call of LastStateId.
-func (mr *MockGenesisContractMockRecorder) LastStateId(arg0 interface{}) *gomock.Call {
+func (mr *MockGenesisContractMockRecorder) LastStateId(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastStateId", reflect.TypeOf((*MockGenesisContract)(nil).LastStateId), arg0)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastStateId", reflect.TypeOf((*MockGenesisContract)(nil).LastStateId), arg0, arg1, arg2)
}
diff --git a/consensus/bor/span_mock.go b/consensus/bor/span_mock.go
index 910e81716c..099807161c 100644
--- a/consensus/bor/span_mock.go
+++ b/consensus/bor/span_mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: consensus/bor/span.go
+// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: Spanner)
// Package bor is a generated GoMock package.
package bor
@@ -42,60 +42,60 @@ func (m *MockSpanner) EXPECT() *MockSpannerMockRecorder {
}
// CommitSpan mocks base method.
-func (m *MockSpanner) CommitSpan(ctx context.Context, heimdallSpan span.HeimdallSpan, state *state.StateDB, header *types.Header, chainContext core.ChainContext) error {
+func (m *MockSpanner) CommitSpan(arg0 context.Context, arg1 span.HeimdallSpan, arg2 *state.StateDB, arg3 *types.Header, arg4 core.ChainContext) error {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "CommitSpan", ctx, heimdallSpan, state, header, chainContext)
+ ret := m.ctrl.Call(m, "CommitSpan", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(error)
return ret0
}
// CommitSpan indicates an expected call of CommitSpan.
-func (mr *MockSpannerMockRecorder) CommitSpan(ctx, heimdallSpan, state, header, chainContext interface{}) *gomock.Call {
+func (mr *MockSpannerMockRecorder) CommitSpan(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitSpan", reflect.TypeOf((*MockSpanner)(nil).CommitSpan), ctx, heimdallSpan, state, header, chainContext)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitSpan", reflect.TypeOf((*MockSpanner)(nil).CommitSpan), arg0, arg1, arg2, arg3, arg4)
}
// GetCurrentSpan mocks base method.
-func (m *MockSpanner) GetCurrentSpan(ctx context.Context, headerHash common.Hash) (*span.Span, error) {
+func (m *MockSpanner) GetCurrentSpan(arg0 context.Context, arg1 common.Hash) (*span.Span, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetCurrentSpan", ctx, headerHash)
+ ret := m.ctrl.Call(m, "GetCurrentSpan", arg0, arg1)
ret0, _ := ret[0].(*span.Span)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentSpan indicates an expected call of GetCurrentSpan.
-func (mr *MockSpannerMockRecorder) GetCurrentSpan(ctx, headerHash interface{}) *gomock.Call {
+func (mr *MockSpannerMockRecorder) GetCurrentSpan(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSpan", reflect.TypeOf((*MockSpanner)(nil).GetCurrentSpan), ctx, headerHash)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSpan", reflect.TypeOf((*MockSpanner)(nil).GetCurrentSpan), arg0, arg1)
}
// GetCurrentValidatorsByBlockNrOrHash mocks base method.
-func (m *MockSpanner) GetCurrentValidatorsByBlockNrOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, blockNumber uint64) ([]*valset.Validator, error) {
+func (m *MockSpanner) GetCurrentValidatorsByBlockNrOrHash(arg0 context.Context, arg1 rpc.BlockNumberOrHash, arg2 uint64) ([]*valset.Validator, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetCurrentValidatorsByBlockNrOrHash", ctx, blockNrOrHash, blockNumber)
+ ret := m.ctrl.Call(m, "GetCurrentValidatorsByBlockNrOrHash", arg0, arg1, arg2)
ret0, _ := ret[0].([]*valset.Validator)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentValidatorsByBlockNrOrHash indicates an expected call of GetCurrentValidatorsByBlockNrOrHash.
-func (mr *MockSpannerMockRecorder) GetCurrentValidatorsByBlockNrOrHash(ctx, blockNrOrHash, blockNumber interface{}) *gomock.Call {
+func (mr *MockSpannerMockRecorder) GetCurrentValidatorsByBlockNrOrHash(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidatorsByBlockNrOrHash", reflect.TypeOf((*MockSpanner)(nil).GetCurrentValidatorsByBlockNrOrHash), ctx, blockNrOrHash, blockNumber)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidatorsByBlockNrOrHash", reflect.TypeOf((*MockSpanner)(nil).GetCurrentValidatorsByBlockNrOrHash), arg0, arg1, arg2)
}
// GetCurrentValidatorsByHash mocks base method.
-func (m *MockSpanner) GetCurrentValidatorsByHash(ctx context.Context, headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error) {
+func (m *MockSpanner) GetCurrentValidatorsByHash(arg0 context.Context, arg1 common.Hash, arg2 uint64) ([]*valset.Validator, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetCurrentValidatorsByHash", ctx, headerHash, blockNumber)
+ ret := m.ctrl.Call(m, "GetCurrentValidatorsByHash", arg0, arg1, arg2)
ret0, _ := ret[0].([]*valset.Validator)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentValidatorsByHash indicates an expected call of GetCurrentValidatorsByHash.
-func (mr *MockSpannerMockRecorder) GetCurrentValidatorsByHash(ctx, headerHash, blockNumber interface{}) *gomock.Call {
+func (mr *MockSpannerMockRecorder) GetCurrentValidatorsByHash(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidatorsByHash", reflect.TypeOf((*MockSpanner)(nil).GetCurrentValidatorsByHash), ctx, headerHash, blockNumber)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidatorsByHash", reflect.TypeOf((*MockSpanner)(nil).GetCurrentValidatorsByHash), arg0, arg1, arg2)
}
diff --git a/core/blockchain.go b/core/blockchain.go
index dd71db0b23..44b0fdee00 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -44,6 +44,7 @@ import (
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/common/tracing"
"github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/core/blockstm"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
@@ -84,10 +85,12 @@ var (
blockImportTimer = metrics.NewRegisteredMeter("chain/imports", nil)
triedbCommitTimer = metrics.NewRegisteredTimer("chain/triedb/commits", nil)
- blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
- blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
- blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil)
- blockWriteTimer = metrics.NewRegisteredTimer("chain/write", nil)
+ blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
+ blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
+ blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil)
+ blockWriteTimer = metrics.NewRegisteredTimer("chain/write", nil)
+ blockExecutionParallelCounter = metrics.NewRegisteredCounter("chain/execution/parallel", nil)
+ blockExecutionSerialCounter = metrics.NewRegisteredCounter("chain/execution/serial", nil)
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
@@ -230,12 +233,13 @@ type BlockChain struct {
stopping atomic.Bool // false if chain is running, true when stopped
procInterrupt atomic.Bool // interrupt signaler for block processing
- engine consensus.Engine
- validator Validator // Block and state validator interface
- prefetcher Prefetcher
- processor Processor // Block transaction processor interface
- forker *ForkChoice
- vmConfig vm.Config
+ engine consensus.Engine
+ validator Validator // Block and state validator interface
+ prefetcher Prefetcher
+ processor Processor // Block transaction processor interface
+ parallelProcessor Processor // Parallel block transaction processor interface
+ forker *ForkChoice
+ vmConfig vm.Config
// Bor related changes
borReceiptsCache *lru.Cache[common.Hash, *types.Receipt] // Cache for the most recent bor receipt receipts per block
@@ -467,6 +471,94 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
return bc, nil
}
+// NewParallelBlockChain , similar to NewBlockChain, creates a new blockchain object, but with a parallel state processor
+// TODO marcello check this func
+func NewParallelBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64, checker ethereum.ChainValidator) (*BlockChain, error) {
+ bc, err := NewBlockChain(db, cacheConfig, chainConfig, engine, vmConfig, shouldPreserve, txLookupLimit, checker)
+
+ if err != nil {
+ return nil, err
+ }
+
+ bc.parallelProcessor = NewParallelStateProcessor(chainConfig, bc, engine)
+
+ return bc, nil
+}
+
+func (bc *BlockChain) ProcessBlock(block *types.Block, parent *types.Header) (types.Receipts, []*types.Log, uint64, *state.StateDB, error) {
+ // Process the block using processor and parallelProcessor at the same time, take the one which finishes first, cancel the other, and return the result
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ type Result struct {
+ receipts types.Receipts
+ logs []*types.Log
+ usedGas uint64
+ err error
+ statedb *state.StateDB
+ counter metrics.Counter
+ }
+
+ resultChan := make(chan Result, 2)
+
+ processorCount := 0
+
+ if bc.parallelProcessor != nil {
+ parallelStatedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
+ if err != nil {
+ return nil, nil, 0, nil, err
+ }
+
+ processorCount++
+
+ go func() {
+ parallelStatedb.StartPrefetcher("chain")
+ receipts, logs, usedGas, err := bc.parallelProcessor.Process(block, parallelStatedb, bc.vmConfig, ctx)
+ resultChan <- Result{receipts, logs, usedGas, err, parallelStatedb, blockExecutionParallelCounter}
+ }()
+ }
+
+ if bc.processor != nil {
+ statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
+ if err != nil {
+ return nil, nil, 0, nil, err
+ }
+
+ processorCount++
+
+ go func() {
+ statedb.StartPrefetcher("chain")
+ receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, ctx)
+ resultChan <- Result{receipts, logs, usedGas, err, statedb, blockExecutionSerialCounter}
+ }()
+ }
+
+ result := <-resultChan
+
+ if _, ok := result.err.(blockstm.ParallelExecFailedError); ok {
+ log.Warn("Parallel state processor failed", "err", result.err)
+
+ // If the parallel processor failed, we will fallback to the serial processor if enabled
+ if processorCount == 2 {
+ result.statedb.StopPrefetcher()
+ result = <-resultChan
+ processorCount--
+ }
+ }
+
+ result.counter.Inc(1)
+
+ // Make sure we are not leaking any prefetchers
+ if processorCount == 2 {
+ go func() {
+ second_result := <-resultChan
+ second_result.statedb.StopPrefetcher()
+ }()
+ }
+
+ return result.receipts, result.logs, result.usedGas, result.statedb, result.err
+}
+
// empty returns an indicator whether the blockchain is empty.
// Note, it's a special case that we connect a non-empty ancient
// database with an empty node, so that we can plugin the ancient
@@ -1933,7 +2025,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
// Process block using the parent state as reference point
pstart := time.Now()
- receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
+ receipts, logs, usedGas, statedb, err := bc.ProcessBlock(block, parent)
+ activeState = statedb
if err != nil {
bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true)
@@ -2769,7 +2862,6 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
return 0, errChainStopped
}
defer bc.chainmu.Unlock()
- // TODO marcello check headerchain method
_, err := bc.hc.InsertHeaderChain(chain, start, bc.forker)
return 0, err
}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 246ff951e8..af2c445adb 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
@@ -158,11 +157,9 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
}
return err
}
- statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache, nil)
- if err != nil {
- return err
- }
- receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
+
+ receipts, _, usedGas, statedb, err := blockchain.ProcessBlock(block, blockchain.GetBlockByHash(block.ParentHash()).Header())
+
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err
@@ -182,6 +179,26 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
return nil
}
+func TestParallelBlockChainImport(t *testing.T) {
+ t.Parallel()
+
+ db, _, blockchain, err := newCanonical(ethash.NewFaker(), 10, true)
+ blockchain.parallelProcessor = NewParallelStateProcessor(blockchain.chainConfig, blockchain, blockchain.engine)
+
+ if err != nil {
+ t.Fatalf("failed to make new canonical chain: %v", err)
+ }
+
+ defer blockchain.Stop()
+
+ block := blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash())
+ blockChainB := makeFakeNonEmptyBlockChain(block, 5, ethash.NewFaker(), db, forkSeed, 5)
+
+ if err := testBlockChainImport(blockChainB, blockchain); err == nil {
+ t.Fatalf("expected error for bad tx")
+ }
+}
+
// testHeaderChainImport tries to process a chain of header, writing them into
// the database if successful.
func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error {
@@ -2027,7 +2044,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*int(DefaultCacheConfig.TriesInMemory), func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2})
if int(b.header.Number.Uint64()) >= mergeBlock {
- // TODO marcello SetPoS everywhere
b.SetPoS()
}
})
diff --git a/core/blockstm/dag.go b/core/blockstm/dag.go
new file mode 100644
index 0000000000..47bd0685a3
--- /dev/null
+++ b/core/blockstm/dag.go
@@ -0,0 +1,201 @@
+package blockstm
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/heimdalr/dag"
+
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type DAG struct {
+ *dag.DAG
+}
+
+type TxDep struct {
+ Index int
+ ReadList []ReadDescriptor
+ FullWriteList [][]WriteDescriptor
+}
+
+func HasReadDep(txFrom TxnOutput, txTo TxnInput) bool {
+ reads := make(map[Key]bool)
+
+ for _, v := range txTo {
+ reads[v.Path] = true
+ }
+
+ for _, rd := range txFrom {
+ if _, ok := reads[rd.Path]; ok {
+ return true
+ }
+ }
+
+ return false
+}
+
+func BuildDAG(deps TxnInputOutput) (d DAG) {
+ d = DAG{dag.NewDAG()}
+ ids := make(map[int]string)
+
+ for i := len(deps.inputs) - 1; i > 0; i-- {
+ txTo := deps.inputs[i]
+
+ var txToId string
+
+ if _, ok := ids[i]; ok {
+ txToId = ids[i]
+ } else {
+ txToId, _ = d.AddVertex(i)
+ ids[i] = txToId
+ }
+
+ for j := i - 1; j >= 0; j-- {
+ txFrom := deps.allOutputs[j]
+
+ if HasReadDep(txFrom, txTo) {
+ var txFromId string
+ if _, ok := ids[j]; ok {
+ txFromId = ids[j]
+ } else {
+ txFromId, _ = d.AddVertex(j)
+ ids[j] = txFromId
+ }
+
+ err := d.AddEdge(txFromId, txToId)
+ if err != nil {
+ log.Warn("Failed to add edge", "from", txFromId, "to", txToId, "err", err)
+ }
+ }
+ }
+ }
+
+ return
+}
+
+func depsHelper(dependencies map[int]map[int]bool, txFrom TxnOutput, txTo TxnInput, i int, j int) map[int]map[int]bool {
+ if HasReadDep(txFrom, txTo) {
+ dependencies[i][j] = true
+
+ for k := range dependencies[i] {
+ _, foundDep := dependencies[j][k]
+
+ if foundDep {
+ delete(dependencies[i], k)
+ }
+ }
+ }
+
+ return dependencies
+}
+
+func UpdateDeps(deps map[int]map[int]bool, t TxDep) map[int]map[int]bool {
+ txTo := t.ReadList
+
+ deps[t.Index] = map[int]bool{}
+
+ for j := 0; j <= t.Index-1; j++ {
+ txFrom := t.FullWriteList[j]
+
+ deps = depsHelper(deps, txFrom, txTo, t.Index, j)
+ }
+
+ return deps
+}
+
+func GetDep(deps TxnInputOutput) map[int]map[int]bool {
+ newDependencies := map[int]map[int]bool{}
+
+ for i := 1; i < len(deps.inputs); i++ {
+ txTo := deps.inputs[i]
+
+ newDependencies[i] = map[int]bool{}
+
+ for j := 0; j <= i-1; j++ {
+ txFrom := deps.allOutputs[j]
+
+ newDependencies = depsHelper(newDependencies, txFrom, txTo, i, j)
+ }
+ }
+
+ return newDependencies
+}
+
+// Find the longest execution path in the DAG
+func (d DAG) LongestPath(stats map[int]ExecutionStat) ([]int, uint64) {
+ prev := make(map[int]int, len(d.GetVertices()))
+
+ for i := 0; i < len(d.GetVertices()); i++ {
+ prev[i] = -1
+ }
+
+ pathWeights := make(map[int]uint64, len(d.GetVertices()))
+
+ maxPath := 0
+ maxPathWeight := uint64(0)
+
+ idxToId := make(map[int]string, len(d.GetVertices()))
+
+ for k, i := range d.GetVertices() {
+ idxToId[i.(int)] = k
+ }
+
+ for i := 0; i < len(idxToId); i++ {
+ parents, _ := d.GetParents(idxToId[i])
+
+ if len(parents) > 0 {
+ for _, p := range parents {
+ weight := pathWeights[p.(int)] + stats[i].End - stats[i].Start
+ if weight > pathWeights[i] {
+ pathWeights[i] = weight
+ prev[i] = p.(int)
+ }
+ }
+ } else {
+ pathWeights[i] = stats[i].End - stats[i].Start
+ }
+
+ if pathWeights[i] > maxPathWeight {
+ maxPath = i
+ maxPathWeight = pathWeights[i]
+ }
+ }
+
+ path := make([]int, 0)
+ for i := maxPath; i != -1; i = prev[i] {
+ path = append(path, i)
+ }
+
+ // Reverse the path so the transactions are in the ascending order
+ for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
+ path[i], path[j] = path[j], path[i]
+ }
+
+ return path, maxPathWeight
+}
+
+func (d DAG) Report(stats map[int]ExecutionStat, out func(string)) {
+ longestPath, weight := d.LongestPath(stats)
+
+ serialWeight := uint64(0)
+
+ for i := 0; i < len(d.GetVertices()); i++ {
+ serialWeight += stats[i].End - stats[i].Start
+ }
+
+ makeStrs := func(ints []int) (ret []string) {
+ for _, v := range ints {
+ ret = append(ret, fmt.Sprint(v))
+ }
+
+ return
+ }
+
+ out("Longest execution path:")
+ out(fmt.Sprintf("(%v) %v", len(longestPath), strings.Join(makeStrs(longestPath), "->")))
+
+ out(fmt.Sprintf("Longest path ideal execution time: %v of %v (serial total), %v%%", time.Duration(weight),
+ time.Duration(serialWeight), fmt.Sprintf("%.1f", float64(weight)*100.0/float64(serialWeight))))
+}
diff --git a/core/blockstm/executor.go b/core/blockstm/executor.go
new file mode 100644
index 0000000000..7ce99f9492
--- /dev/null
+++ b/core/blockstm/executor.go
@@ -0,0 +1,641 @@
+package blockstm
+
+import (
+ "container/heap"
+ "context"
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type ExecResult struct {
+ err error
+ ver Version
+ txIn TxnInput
+ txOut TxnOutput
+ txAllOut TxnOutput
+}
+
+type ExecTask interface {
+ Execute(mvh *MVHashMap, incarnation int) error
+ MVReadList() []ReadDescriptor
+ MVWriteList() []WriteDescriptor
+ MVFullWriteList() []WriteDescriptor
+ Hash() common.Hash
+ Sender() common.Address
+ Settle()
+ Dependencies() []int
+}
+
+type ExecVersionView struct {
+ ver Version
+ et ExecTask
+ mvh *MVHashMap
+ sender common.Address
+}
+
+var NumSpeculativeProcs int = 8
+
+func SetProcs(specProcs int) {
+ NumSpeculativeProcs = specProcs
+}
+
+func (ev *ExecVersionView) Execute() (er ExecResult) {
+ er.ver = ev.ver
+ if er.err = ev.et.Execute(ev.mvh, ev.ver.Incarnation); er.err != nil {
+ return
+ }
+
+ er.txIn = ev.et.MVReadList()
+ er.txOut = ev.et.MVWriteList()
+ er.txAllOut = ev.et.MVFullWriteList()
+
+ return
+}
+
+type ErrExecAbortError struct {
+ Dependency int
+ OriginError error
+}
+
+func (e ErrExecAbortError) Error() string {
+ if e.Dependency >= 0 {
+ return fmt.Sprintf("Execution aborted due to dependency %d", e.Dependency)
+ } else {
+ return "Execution aborted"
+ }
+}
+
+type ParallelExecFailedError struct {
+ Msg string
+}
+
+func (e ParallelExecFailedError) Error() string {
+ return e.Msg
+}
+
+type IntHeap []int
+
+func (h IntHeap) Len() int { return len(h) }
+func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
+func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
+
+func (h *IntHeap) Push(x any) {
+ // Push and Pop use pointer receivers because they modify the slice's length,
+ // not just its contents.
+ *h = append(*h, x.(int))
+}
+
+func (h *IntHeap) Pop() any {
+ old := *h
+ n := len(old)
+ x := old[n-1]
+ *h = old[0 : n-1]
+
+ return x
+}
+
+type SafeQueue interface {
+ Push(v int, d interface{})
+ Pop() interface{}
+ Len() int
+}
+
+type SafeFIFOQueue struct {
+ c chan interface{}
+}
+
+func NewSafeFIFOQueue(capacity int) *SafeFIFOQueue {
+ return &SafeFIFOQueue{
+ c: make(chan interface{}, capacity),
+ }
+}
+
+func (q *SafeFIFOQueue) Push(v int, d interface{}) {
+ q.c <- d
+}
+
+func (q *SafeFIFOQueue) Pop() interface{} {
+ return <-q.c
+}
+
+func (q *SafeFIFOQueue) Len() int {
+ return len(q.c)
+}
+
+// A thread safe priority queue
+type SafePriorityQueue struct {
+ m sync.Mutex
+ queue *IntHeap
+ data map[int]interface{}
+}
+
+func NewSafePriorityQueue(capacity int) *SafePriorityQueue {
+ q := make(IntHeap, 0, capacity)
+
+ return &SafePriorityQueue{
+ m: sync.Mutex{},
+ queue: &q,
+ data: make(map[int]interface{}, capacity),
+ }
+}
+
+func (pq *SafePriorityQueue) Push(v int, d interface{}) {
+ pq.m.Lock()
+
+ heap.Push(pq.queue, v)
+ pq.data[v] = d
+
+ pq.m.Unlock()
+}
+
+func (pq *SafePriorityQueue) Pop() interface{} {
+ pq.m.Lock()
+ defer pq.m.Unlock()
+
+ v := heap.Pop(pq.queue).(int)
+
+ return pq.data[v]
+}
+
+func (pq *SafePriorityQueue) Len() int {
+ return pq.queue.Len()
+}
+
+type ParallelExecutionResult struct {
+ TxIO *TxnInputOutput
+ Stats *map[int]ExecutionStat
+ Deps *DAG
+ AllDeps map[int]map[int]bool
+}
+
+const numGoProcs = 1
+
+type ParallelExecutor struct {
+ tasks []ExecTask
+
+ // Stores the execution statistics for the last incarnation of each task
+ stats map[int]ExecutionStat
+
+ statsMutex sync.Mutex
+
+ // Channel for tasks that should be prioritized
+ chTasks chan ExecVersionView
+
+ // Channel for speculative tasks
+ chSpeculativeTasks chan struct{}
+
+ // Channel to signal that the result of a transaction could be written to storage
+ specTaskQueue SafeQueue
+
+ // A priority queue that stores speculative tasks
+ chSettle chan int
+
+ // Channel to signal that a transaction has finished executing
+ chResults chan struct{}
+
+ // A priority queue that stores the transaction index of results, so we can validate the results in order
+ resultQueue SafeQueue
+
+ // A wait group to wait for all settling tasks to finish
+ settleWg sync.WaitGroup
+
+ // An integer that tracks the index of last settled transaction
+ lastSettled int
+
+ // For a task that runs only after all of its preceding tasks have finished and passed validation,
+ // its result will be absolutely valid and therefore its validation could be skipped.
+ // This map stores the boolean value indicating whether a task satisfy this condition ( absolutely valid).
+ skipCheck map[int]bool
+
+ // Execution tasks stores the state of each execution task
+ execTasks taskStatusManager
+
+ // Validate tasks stores the state of each validation task
+ validateTasks taskStatusManager
+
+ // Stats for debugging purposes
+ cntExec, cntSuccess, cntAbort, cntTotalValidations, cntValidationFail int
+
+ diagExecSuccess, diagExecAbort []int
+
+ // Multi-version hash map
+ mvh *MVHashMap
+
+ // Stores the inputs and outputs of the last incardanotion of all transactions
+ lastTxIO *TxnInputOutput
+
+ // Tracks the incarnation number of each transaction
+ txIncarnations []int
+
+ // A map that stores the estimated dependency of a transaction if it is aborted without any known dependency
+ estimateDeps map[int][]int
+
+ // A map that records whether a transaction result has been speculatively validated
+ preValidated map[int]bool
+
+ // Time records when the parallel execution starts
+ begin time.Time
+
+ // Enable profiling
+ profile bool
+
+ // Worker wait group
+ workerWg sync.WaitGroup
+}
+
+type ExecutionStat struct {
+ TxIdx int
+ Incarnation int
+ Start uint64
+ End uint64
+ Worker int
+}
+
+func NewParallelExecutor(tasks []ExecTask, profile bool, metadata bool) *ParallelExecutor {
+ numTasks := len(tasks)
+
+ var resultQueue SafeQueue
+
+ var specTaskQueue SafeQueue
+
+ if metadata {
+ resultQueue = NewSafeFIFOQueue(numTasks)
+ specTaskQueue = NewSafeFIFOQueue(numTasks)
+ } else {
+ resultQueue = NewSafePriorityQueue(numTasks)
+ specTaskQueue = NewSafePriorityQueue(numTasks)
+ }
+
+ pe := &ParallelExecutor{
+ tasks: tasks,
+ stats: make(map[int]ExecutionStat, numTasks),
+ chTasks: make(chan ExecVersionView, numTasks),
+ chSpeculativeTasks: make(chan struct{}, numTasks),
+ chSettle: make(chan int, numTasks),
+ chResults: make(chan struct{}, numTasks),
+ specTaskQueue: specTaskQueue,
+ resultQueue: resultQueue,
+ lastSettled: -1,
+ skipCheck: make(map[int]bool),
+ execTasks: makeStatusManager(numTasks),
+ validateTasks: makeStatusManager(0),
+ diagExecSuccess: make([]int, numTasks),
+ diagExecAbort: make([]int, numTasks),
+ mvh: MakeMVHashMap(),
+ lastTxIO: MakeTxnInputOutput(numTasks),
+ txIncarnations: make([]int, numTasks),
+ estimateDeps: make(map[int][]int),
+ preValidated: make(map[int]bool),
+ begin: time.Now(),
+ profile: profile,
+ }
+
+ return pe
+}
+
+// nolint: gocognit
+func (pe *ParallelExecutor) Prepare() error {
+ prevSenderTx := make(map[common.Address]int)
+
+ for i, t := range pe.tasks {
+ clearPendingFlag := false
+
+ pe.skipCheck[i] = false
+ pe.estimateDeps[i] = make([]int, 0)
+
+ if len(t.Dependencies()) > 0 {
+ for _, val := range t.Dependencies() {
+ clearPendingFlag = true
+
+ pe.execTasks.addDependencies(val, i)
+ }
+
+ if clearPendingFlag {
+ pe.execTasks.clearPending(i)
+
+ clearPendingFlag = false
+ }
+ } else {
+ if tx, ok := prevSenderTx[t.Sender()]; ok {
+ pe.execTasks.addDependencies(tx, i)
+ pe.execTasks.clearPending(i)
+ }
+
+ prevSenderTx[t.Sender()] = i
+ }
+ }
+
+ pe.workerWg.Add(NumSpeculativeProcs + numGoProcs)
+
+ // Launch workers that execute transactions
+ for i := 0; i < NumSpeculativeProcs+numGoProcs; i++ {
+ go func(procNum int) {
+ defer pe.workerWg.Done()
+
+ doWork := func(task ExecVersionView) {
+ start := time.Duration(0)
+ if pe.profile {
+ start = time.Since(pe.begin)
+ }
+
+ res := task.Execute()
+
+ if res.err == nil {
+ pe.mvh.FlushMVWriteSet(res.txAllOut)
+ }
+
+ pe.resultQueue.Push(res.ver.TxnIndex, res)
+ pe.chResults <- struct{}{}
+
+ if pe.profile {
+ end := time.Since(pe.begin)
+
+ pe.statsMutex.Lock()
+ pe.stats[res.ver.TxnIndex] = ExecutionStat{
+ TxIdx: res.ver.TxnIndex,
+ Incarnation: res.ver.Incarnation,
+ Start: uint64(start),
+ End: uint64(end),
+ Worker: procNum,
+ }
+ pe.statsMutex.Unlock()
+ }
+ }
+
+ if procNum < NumSpeculativeProcs {
+ for range pe.chSpeculativeTasks {
+ doWork(pe.specTaskQueue.Pop().(ExecVersionView))
+ }
+ } else {
+ for task := range pe.chTasks {
+ doWork(task)
+ }
+ }
+ }(i)
+ }
+
+ pe.settleWg.Add(1)
+
+ go func() {
+ for t := range pe.chSettle {
+ pe.tasks[t].Settle()
+ }
+
+ pe.settleWg.Done()
+ }()
+
+ // bootstrap first execution
+ tx := pe.execTasks.takeNextPending()
+
+ if tx == -1 {
+ return ParallelExecFailedError{"no executable transactions due to bad dependency"}
+ }
+
+ pe.cntExec++
+
+ pe.chTasks <- ExecVersionView{ver: Version{tx, 0}, et: pe.tasks[tx], mvh: pe.mvh, sender: pe.tasks[tx].Sender()}
+
+ return nil
+}
+
+func (pe *ParallelExecutor) Close(wait bool) {
+ close(pe.chTasks)
+ close(pe.chSpeculativeTasks)
+ close(pe.chSettle)
+
+ if wait {
+ pe.workerWg.Wait()
+ }
+
+ if wait {
+ pe.settleWg.Wait()
+ }
+}
+
+// nolint: gocognit
+func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResult, err error) {
+ tx := res.ver.TxnIndex
+
+ if abortErr, ok := res.err.(ErrExecAbortError); ok && abortErr.OriginError != nil && pe.skipCheck[tx] {
+ // If the transaction failed when we know it should not fail, this means the transaction itself is
+ // bad (e.g. wrong nonce), and we should exit the execution immediately
+ err = fmt.Errorf("could not apply tx %d [%v]: %w", tx, pe.tasks[tx].Hash(), abortErr.OriginError)
+ pe.Close(true)
+
+ return
+ }
+
+ // nolint: nestif
+ if execErr, ok := res.err.(ErrExecAbortError); ok {
+ addedDependencies := false
+
+ if execErr.Dependency >= 0 {
+ l := len(pe.estimateDeps[tx])
+ for l > 0 && pe.estimateDeps[tx][l-1] > execErr.Dependency {
+ pe.execTasks.removeDependency(pe.estimateDeps[tx][l-1])
+ pe.estimateDeps[tx] = pe.estimateDeps[tx][:l-1]
+ l--
+ }
+
+ addedDependencies = pe.execTasks.addDependencies(execErr.Dependency, tx)
+ } else {
+ estimate := 0
+
+ if len(pe.estimateDeps[tx]) > 0 {
+ estimate = pe.estimateDeps[tx][len(pe.estimateDeps[tx])-1]
+ }
+ addedDependencies = pe.execTasks.addDependencies(estimate, tx)
+ newEstimate := estimate + (estimate+tx)/2
+ if newEstimate >= tx {
+ newEstimate = tx - 1
+ }
+ pe.estimateDeps[tx] = append(pe.estimateDeps[tx], newEstimate)
+ }
+
+ pe.execTasks.clearInProgress(tx)
+
+ if !addedDependencies {
+ pe.execTasks.pushPending(tx)
+ }
+ pe.txIncarnations[tx]++
+ pe.diagExecAbort[tx]++
+ pe.cntAbort++
+ } else {
+ pe.lastTxIO.recordRead(tx, res.txIn)
+
+ if res.ver.Incarnation == 0 {
+ pe.lastTxIO.recordWrite(tx, res.txOut)
+ pe.lastTxIO.recordAllWrite(tx, res.txAllOut)
+ } else {
+ if res.txAllOut.hasNewWrite(pe.lastTxIO.AllWriteSet(tx)) {
+ pe.validateTasks.pushPendingSet(pe.execTasks.getRevalidationRange(tx + 1))
+ }
+
+ prevWrite := pe.lastTxIO.AllWriteSet(tx)
+
+ // Remove entries that were previously written but are no longer written
+
+ cmpMap := make(map[Key]bool)
+
+ for _, w := range res.txAllOut {
+ cmpMap[w.Path] = true
+ }
+
+ for _, v := range prevWrite {
+ if _, ok := cmpMap[v.Path]; !ok {
+ pe.mvh.Delete(v.Path, tx)
+ }
+ }
+
+ pe.lastTxIO.recordWrite(tx, res.txOut)
+ pe.lastTxIO.recordAllWrite(tx, res.txAllOut)
+ }
+
+ pe.validateTasks.pushPending(tx)
+ pe.execTasks.markComplete(tx)
+ pe.diagExecSuccess[tx]++
+ pe.cntSuccess++
+
+ pe.execTasks.removeDependency(tx)
+ }
+
+ // do validations ...
+ maxComplete := pe.execTasks.maxAllComplete()
+
+ toValidate := make([]int, 0, 2)
+
+ for pe.validateTasks.minPending() <= maxComplete && pe.validateTasks.minPending() >= 0 {
+ toValidate = append(toValidate, pe.validateTasks.takeNextPending())
+ }
+
+ for i := 0; i < len(toValidate); i++ {
+ pe.cntTotalValidations++
+
+ tx := toValidate[i]
+
+ if pe.skipCheck[tx] || ValidateVersion(tx, pe.lastTxIO, pe.mvh) {
+ pe.validateTasks.markComplete(tx)
+ } else {
+ pe.cntValidationFail++
+ pe.diagExecAbort[tx]++
+ for _, v := range pe.lastTxIO.AllWriteSet(tx) {
+ pe.mvh.MarkEstimate(v.Path, tx)
+ }
+ // 'create validation tasks for all transactions > tx ...'
+ pe.validateTasks.pushPendingSet(pe.execTasks.getRevalidationRange(tx + 1))
+ pe.validateTasks.clearInProgress(tx) // clear in progress - pending will be added again once new incarnation executes
+
+ pe.execTasks.clearComplete(tx)
+ pe.execTasks.pushPending(tx)
+
+ pe.preValidated[tx] = false
+ pe.txIncarnations[tx]++
+ }
+ }
+
+ // Settle transactions that have been validated to be correct and that won't be re-executed again
+ maxValidated := pe.validateTasks.maxAllComplete()
+
+ for pe.lastSettled < maxValidated {
+ pe.lastSettled++
+ if pe.execTasks.checkInProgress(pe.lastSettled) || pe.execTasks.checkPending(pe.lastSettled) || pe.execTasks.isBlocked(pe.lastSettled) {
+ pe.lastSettled--
+ break
+ }
+ pe.chSettle <- pe.lastSettled
+ }
+
+ if pe.validateTasks.countComplete() == len(pe.tasks) && pe.execTasks.countComplete() == len(pe.tasks) {
+ log.Debug("blockstm exec summary", "execs", pe.cntExec, "success", pe.cntSuccess, "aborts", pe.cntAbort, "validations", pe.cntTotalValidations, "failures", pe.cntValidationFail, "#tasks/#execs", fmt.Sprintf("%.2f%%", float64(len(pe.tasks))/float64(pe.cntExec)*100))
+
+ pe.Close(true)
+
+ var allDeps map[int]map[int]bool
+
+ var deps DAG
+
+ if pe.profile {
+ allDeps = GetDep(*pe.lastTxIO)
+ deps = BuildDAG(*pe.lastTxIO)
+ }
+
+ return ParallelExecutionResult{pe.lastTxIO, &pe.stats, &deps, allDeps}, err
+ }
+
+ // Send the next immediate pending transaction to be executed
+ if pe.execTasks.minPending() != -1 && pe.execTasks.minPending() == maxValidated+1 {
+ nextTx := pe.execTasks.takeNextPending()
+ if nextTx != -1 {
+ pe.cntExec++
+
+ pe.skipCheck[nextTx] = true
+
+ pe.chTasks <- ExecVersionView{ver: Version{nextTx, pe.txIncarnations[nextTx]}, et: pe.tasks[nextTx], mvh: pe.mvh, sender: pe.tasks[nextTx].Sender()}
+ }
+ }
+
+ // Send speculative tasks
+ for pe.execTasks.minPending() != -1 {
+ nextTx := pe.execTasks.takeNextPending()
+
+ if nextTx != -1 {
+ pe.cntExec++
+
+ task := ExecVersionView{ver: Version{nextTx, pe.txIncarnations[nextTx]}, et: pe.tasks[nextTx], mvh: pe.mvh, sender: pe.tasks[nextTx].Sender()}
+
+ pe.specTaskQueue.Push(nextTx, task)
+ pe.chSpeculativeTasks <- struct{}{}
+ }
+ }
+
+ return
+}
+
+type PropertyCheck func(*ParallelExecutor) error
+
+func executeParallelWithCheck(tasks []ExecTask, profile bool, check PropertyCheck, metadata bool, interruptCtx context.Context) (result ParallelExecutionResult, err error) {
+ if len(tasks) == 0 {
+ return ParallelExecutionResult{MakeTxnInputOutput(len(tasks)), nil, nil, nil}, nil
+ }
+
+ pe := NewParallelExecutor(tasks, profile, metadata)
+ err = pe.Prepare()
+
+ if err != nil {
+ pe.Close(true)
+ return
+ }
+
+ for range pe.chResults {
+ if interruptCtx != nil && interruptCtx.Err() != nil {
+ pe.Close(true)
+ return result, interruptCtx.Err()
+ }
+
+ res := pe.resultQueue.Pop().(ExecResult)
+
+ result, err = pe.Step(&res)
+
+ if err != nil {
+ return result, err
+ }
+
+ if check != nil {
+ err = check(pe)
+ }
+
+ if result.TxIO != nil || err != nil {
+ return result, err
+ }
+ }
+
+ return
+}
+
+func ExecuteParallel(tasks []ExecTask, profile bool, metadata bool, interruptCtx context.Context) (result ParallelExecutionResult, err error) {
+ return executeParallelWithCheck(tasks, profile, nil, metadata, interruptCtx)
+}
diff --git a/core/blockstm/executor_test.go b/core/blockstm/executor_test.go
new file mode 100644
index 0000000000..511d9c774a
--- /dev/null
+++ b/core/blockstm/executor_test.go
@@ -0,0 +1,984 @@
+package blockstm
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "math/rand"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type OpType int
+
+const readType = 0
+const writeType = 1
+const otherType = 2
+const greenTick = "✅"
+const redCross = "❌"
+
+const threeRockets = "🚀🚀🚀"
+
+type Op struct {
+ key Key
+ duration time.Duration
+ opType OpType
+ val int
+}
+
+type testExecTask struct {
+ txIdx int
+ ops []Op
+ readMap map[Key]ReadDescriptor
+ writeMap map[Key]WriteDescriptor
+ sender common.Address
+ nonce int
+ dependencies []int
+}
+
+type PathGenerator func(addr common.Address, i int, j int, total int) Key
+
+type TaskRunner func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration)
+
+type TaskRunnerWithMetadata func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration)
+
+type Timer func(txIdx int, opIdx int) time.Duration
+
+type Sender func(int) common.Address
+
+func NewTestExecTask(txIdx int, ops []Op, sender common.Address, nonce int) *testExecTask {
+ return &testExecTask{
+ txIdx: txIdx,
+ ops: ops,
+ readMap: make(map[Key]ReadDescriptor),
+ writeMap: make(map[Key]WriteDescriptor),
+ sender: sender,
+ nonce: nonce,
+ dependencies: []int{},
+ }
+}
+
+func sleep(i time.Duration) {
+ start := time.Now()
+ for time.Since(start) < i {
+ }
+}
+
+func (t *testExecTask) Execute(mvh *MVHashMap, incarnation int) error {
+ // Sleep for 50 microsecond to simulate setup time
+ sleep(time.Microsecond * 50)
+
+ version := Version{TxnIndex: t.txIdx, Incarnation: incarnation}
+
+ t.readMap = make(map[Key]ReadDescriptor)
+ t.writeMap = make(map[Key]WriteDescriptor)
+
+ deps := -1
+
+ for i, op := range t.ops {
+ k := op.key
+
+ switch op.opType {
+ case readType:
+ if _, ok := t.writeMap[k]; ok {
+ sleep(op.duration)
+ continue
+ }
+
+ result := mvh.Read(k, t.txIdx)
+
+ val := result.Value()
+
+ if i == 0 && val != nil && (val.(int) != t.nonce) {
+ return ErrExecAbortError{}
+ }
+
+ if result.Status() == MVReadResultDependency {
+ if result.depIdx > deps {
+ deps = result.depIdx
+ }
+ }
+
+ var readKind int
+
+ if result.Status() == MVReadResultDone {
+ readKind = ReadKindMap
+ } else if result.Status() == MVReadResultNone {
+ readKind = ReadKindStorage
+ }
+
+ sleep(op.duration)
+
+ t.readMap[k] = ReadDescriptor{k, readKind, Version{TxnIndex: result.depIdx, Incarnation: result.incarnation}}
+ case writeType:
+ t.writeMap[k] = WriteDescriptor{k, version, op.val}
+ case otherType:
+ sleep(op.duration)
+ default:
+ panic(fmt.Sprintf("Unknown op type: %d", op.opType))
+ }
+ }
+
+ if deps != -1 {
+ return ErrExecAbortError{deps, fmt.Errorf("Dependency error")}
+ }
+
+ return nil
+}
+
+func (t *testExecTask) MVWriteList() []WriteDescriptor {
+ return t.MVFullWriteList()
+}
+
+func (t *testExecTask) MVFullWriteList() []WriteDescriptor {
+ writes := make([]WriteDescriptor, 0, len(t.writeMap))
+
+ for _, v := range t.writeMap {
+ writes = append(writes, v)
+ }
+
+ return writes
+}
+
+func (t *testExecTask) MVReadList() []ReadDescriptor {
+ reads := make([]ReadDescriptor, 0, len(t.readMap))
+
+ for _, v := range t.readMap {
+ reads = append(reads, v)
+ }
+
+ return reads
+}
+
+func (t *testExecTask) Settle() {}
+
+func (t *testExecTask) Sender() common.Address {
+ return t.sender
+}
+
+func (t *testExecTask) Hash() common.Hash {
+ return common.BytesToHash([]byte(fmt.Sprintf("%d", t.txIdx)))
+}
+
+func (t *testExecTask) Dependencies() []int {
+ return t.dependencies
+}
+
+func randTimeGenerator(min time.Duration, max time.Duration) func(txIdx int, opIdx int) time.Duration {
+ return func(txIdx int, opIdx int) time.Duration {
+ return time.Duration(rand.Int63n(int64(max-min))) + min
+ }
+}
+
+func longTailTimeGenerator(min time.Duration, max time.Duration, i int, j int) func(txIdx int, opIdx int) time.Duration {
+ return func(txIdx int, opIdx int) time.Duration {
+ if txIdx%i == 0 && opIdx == j {
+ return max * 100
+ } else {
+ return time.Duration(rand.Int63n(int64(max-min))) + min
+ }
+ }
+}
+
+var randomPathGenerator = func(sender common.Address, i int, j int, total int) Key {
+ return NewStateKey(common.BigToAddress((big.NewInt(int64(i % 10)))), common.BigToHash((big.NewInt(int64(total)))))
+}
+
+var dexPathGenerator = func(sender common.Address, i int, j int, total int) Key {
+ if j == total-1 || j == 2 {
+ return NewSubpathKey(common.BigToAddress(big.NewInt(int64(0))), 1)
+ } else {
+ return NewSubpathKey(common.BigToAddress(big.NewInt(int64(j))), 1)
+ }
+}
+
+var readTime = randTimeGenerator(4*time.Microsecond, 12*time.Microsecond)
+var writeTime = randTimeGenerator(2*time.Microsecond, 6*time.Microsecond)
+var nonIOTime = randTimeGenerator(1*time.Microsecond, 2*time.Microsecond)
+
+func taskFactory(numTask int, sender Sender, readsPerT int, writesPerT int, nonIOPerT int, pathGenerator PathGenerator, readTime Timer, writeTime Timer, nonIOTime Timer) ([]ExecTask, time.Duration) {
+ exec := make([]ExecTask, 0, numTask)
+
+ var serialDuration time.Duration
+
+ senderNonces := make(map[common.Address]int)
+
+ for i := 0; i < numTask; i++ {
+ s := sender(i)
+
+ // Set first two ops to always read and write nonce
+ ops := make([]Op, 0, readsPerT+writesPerT+nonIOPerT)
+
+ ops = append(ops, Op{opType: readType, key: NewSubpathKey(s, 2), duration: readTime(i, 0), val: senderNonces[s]})
+
+ senderNonces[s]++
+
+ ops = append(ops, Op{opType: writeType, key: NewSubpathKey(s, 2), duration: writeTime(i, 1), val: senderNonces[s]})
+
+ for j := 0; j < readsPerT-1; j++ {
+ ops = append(ops, Op{opType: readType})
+ }
+
+ for j := 0; j < nonIOPerT; j++ {
+ ops = append(ops, Op{opType: otherType})
+ }
+
+ for j := 0; j < writesPerT-1; j++ {
+ ops = append(ops, Op{opType: writeType})
+ }
+
+ // shuffle ops except for the first three (read nonce, write nonce, another read) ops and last write op.
+ // This enables random path generator to generate deterministic paths for these "special" ops.
+ for j := 3; j < len(ops)-1; j++ {
+ k := rand.Intn(len(ops)-j-1) + j
+ ops[j], ops[k] = ops[k], ops[j]
+ }
+
+ // Generate time and key path for each op except first two that are always read and write nonce
+ for j := 2; j < len(ops); j++ {
+ if ops[j].opType == readType {
+ ops[j].key = pathGenerator(s, i, j, len(ops))
+ ops[j].duration = readTime(i, j)
+ } else if ops[j].opType == writeType {
+ ops[j].key = pathGenerator(s, i, j, len(ops))
+ ops[j].duration = writeTime(i, j)
+ } else {
+ ops[j].duration = nonIOTime(i, j)
+ }
+
+ serialDuration += ops[j].duration
+ }
+
+ if ops[len(ops)-1].opType != writeType {
+ panic("Last op must be a write")
+ }
+
+ t := NewTestExecTask(i, ops, s, senderNonces[s]-1)
+ exec = append(exec, t)
+ }
+
+ return exec, serialDuration
+}
+
+func testExecutorComb(t *testing.T, totalTxs []int, numReads []int, numWrites []int, numNonIO []int, taskRunner TaskRunner) {
+ t.Helper()
+ log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
+
+ improved := 0
+ total := 0
+
+ totalExecDuration := time.Duration(0)
+ totalSerialDuration := time.Duration(0)
+
+ for _, numTx := range totalTxs {
+ for _, numRead := range numReads {
+ for _, numWrite := range numWrites {
+ for _, numNonIO := range numNonIO {
+ log.Info("Executing block", "numTx", numTx, "numRead", numRead, "numWrite", numWrite, "numNonIO", numNonIO)
+ execDuration, expectedSerialDuration := taskRunner(numTx, numRead, numWrite, numNonIO)
+
+ if execDuration < expectedSerialDuration {
+ improved++
+ }
+ total++
+
+ performance := greenTick
+
+ if execDuration >= expectedSerialDuration {
+ performance = redCross
+ }
+
+ fmt.Printf("exec duration %v, serial duration %v, time reduced %v %.2f%%, %v \n", execDuration, expectedSerialDuration, expectedSerialDuration-execDuration, float64(expectedSerialDuration-execDuration)/float64(expectedSerialDuration)*100, performance)
+
+ totalExecDuration += execDuration
+ totalSerialDuration += expectedSerialDuration
+ }
+ }
+ }
+ }
+
+ fmt.Println("Improved: ", improved, "Total: ", total, "success rate: ", float64(improved)/float64(total)*100)
+ fmt.Printf("Total exec duration: %v, total serial duration: %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDuration, totalSerialDuration, totalSerialDuration-totalExecDuration, float64(totalSerialDuration-totalExecDuration)/float64(totalSerialDuration)*100)
+}
+
+// nolint: gocognit
+func testExecutorCombWithMetadata(t *testing.T, totalTxs []int, numReads []int, numWrites []int, numNonIOs []int, taskRunner TaskRunnerWithMetadata) {
+ t.Helper()
+ log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
+
+ improved := 0
+ improvedMetadata := 0
+ rocket := 0
+ total := 0
+
+ totalExecDuration := time.Duration(0)
+ totalExecDurationMetadata := time.Duration(0)
+ totalSerialDuration := time.Duration(0)
+
+ for _, numTx := range totalTxs {
+ for _, numRead := range numReads {
+ for _, numWrite := range numWrites {
+ for _, numNonIO := range numNonIOs {
+ log.Info("Executing block", "numTx", numTx, "numRead", numRead, "numWrite", numWrite, "numNonIO", numNonIO)
+ execDuration, execDurationMetadata, expectedSerialDuration := taskRunner(numTx, numRead, numWrite, numNonIO)
+
+ if execDuration < expectedSerialDuration {
+ improved++
+ }
+ total++
+
+ performance := greenTick
+
+ if execDuration >= expectedSerialDuration {
+ performance = redCross
+
+ if execDurationMetadata <= expectedSerialDuration {
+ performance = threeRockets
+ rocket++
+ }
+ }
+
+ if execDuration >= execDurationMetadata {
+ improvedMetadata++
+ }
+
+ fmt.Printf("WITHOUT METADATA: exec duration %v, serial duration %v, time reduced %v %.2f%%, %v \n", execDuration, expectedSerialDuration, expectedSerialDuration-execDuration, float64(expectedSerialDuration-execDuration)/float64(expectedSerialDuration)*100, performance)
+ fmt.Printf("WITH METADATA: exec duration %v, exec duration with metadata %v, time reduced %v %.2f%%\n", execDuration, execDurationMetadata, execDuration-execDurationMetadata, float64(execDuration-execDurationMetadata)/float64(execDuration)*100)
+
+ totalExecDuration += execDuration
+ totalExecDurationMetadata += execDurationMetadata
+ totalSerialDuration += expectedSerialDuration
+ }
+ }
+ }
+ }
+
+ fmt.Println("\nImproved: ", improved, "Total: ", total, "success rate: ", float64(improved)/float64(total)*100)
+ fmt.Println("Metadata Better: ", improvedMetadata, "out of: ", total, "success rate: ", float64(improvedMetadata)/float64(total)*100)
+ fmt.Println("Rockets (Time of: metadata < serial < without metadata): ", rocket)
+ fmt.Printf("\nWithout metadata <> serial: Total exec duration: %v, total serial duration : %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDuration, totalSerialDuration, totalSerialDuration-totalExecDuration, float64(totalSerialDuration-totalExecDuration)/float64(totalSerialDuration)*100)
+ fmt.Printf("With metadata <> serial: Total exec duration metadata: %v, total serial duration : %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDurationMetadata, totalSerialDuration, totalSerialDuration-totalExecDurationMetadata, float64(totalSerialDuration-totalExecDurationMetadata)/float64(totalSerialDuration)*100)
+ fmt.Printf("Without metadata <> with metadata: Total exec duration: %v, total exec duration metadata: %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDuration, totalExecDurationMetadata, totalExecDuration-totalExecDurationMetadata, float64(totalExecDuration-totalExecDurationMetadata)/float64(totalExecDuration)*100)
+}
+
+func composeValidations(checks []PropertyCheck) PropertyCheck {
+ return func(pe *ParallelExecutor) error {
+ for _, check := range checks {
+ err := check(pe)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+}
+
+func checkNoStatusOverlap(pe *ParallelExecutor) error {
+ seen := make(map[int]string)
+
+ for _, tx := range pe.execTasks.complete {
+ seen[tx] = "complete"
+ }
+
+ for _, tx := range pe.execTasks.inProgress {
+ if v, ok := seen[tx]; ok {
+ return fmt.Errorf("tx %v is in both %v and inProgress", v, tx)
+ }
+
+ seen[tx] = "inProgress"
+ }
+
+ for _, tx := range pe.execTasks.pending {
+ if v, ok := seen[tx]; ok {
+ return fmt.Errorf("tx %v is in both %v complete and pending", v, tx)
+ }
+
+ seen[tx] = "pending"
+ }
+
+ return nil
+}
+
+func checkNoDroppedTx(pe *ParallelExecutor) error {
+ for i := 0; i < len(pe.tasks); i++ {
+ if !pe.execTasks.checkComplete(i) && !pe.execTasks.checkInProgress(i) && !pe.execTasks.checkPending(i) {
+ if !pe.execTasks.isBlocked(i) {
+ return fmt.Errorf("tx %v is not in any status and is not blocked by any other tx", i)
+ }
+ }
+ }
+
+ return nil
+}
+
+// nolint: unparam
+func runParallel(t *testing.T, tasks []ExecTask, validation PropertyCheck, metadata bool) time.Duration {
+ t.Helper()
+
+ profile := false
+
+ start := time.Now()
+ result, err := executeParallelWithCheck(tasks, false, validation, metadata, nil)
+
+ if result.Deps != nil && profile {
+ result.Deps.Report(*result.Stats, func(str string) { fmt.Println(str) })
+ }
+
+ assert.NoError(t, err, "error occur during parallel execution")
+
+ // Need to apply the final write set to storage
+
+ finalWriteSet := make(map[Key]time.Duration)
+
+ for _, task := range tasks {
+ task := task.(*testExecTask)
+ for _, op := range task.ops {
+ if op.opType == writeType {
+ finalWriteSet[op.key] = op.duration
+ }
+ }
+ }
+
+ for _, v := range finalWriteSet {
+ sleep(v)
+ }
+
+ duration := time.Since(start)
+
+ return duration
+}
+
+func runParallelGetMetadata(t *testing.T, tasks []ExecTask, validation PropertyCheck) map[int]map[int]bool {
+ t.Helper()
+
+ res, err := executeParallelWithCheck(tasks, true, validation, false, nil)
+
+ assert.NoError(t, err, "error occur during parallel execution")
+
+ return res.AllDeps
+}
+
+func TestLessConflicts(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{10, 50, 100, 200, 300}
+ numReads := []int{20, 100, 200}
+ numWrites := []int{20, 100, 200}
+ numNonIO := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration) {
+ sender := func(i int) common.Address {
+ randomness := rand.Intn(10) + 10
+ return common.BigToAddress(big.NewInt(int64(i % randomness)))
+ }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ return runParallel(t, tasks, checks, false), serialDuration
+ }
+
+ testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestLessConflictsWithMetadata(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{300}
+ numReads := []int{100, 200}
+ numWrites := []int{100, 200}
+ numNonIOs := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
+ sender := func(i int) common.Address {
+ randomness := rand.Intn(10) + 10
+ return common.BigToAddress(big.NewInt(int64(i % randomness)))
+ }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ parallelDuration := runParallel(t, tasks, checks, false)
+
+ allDeps := runParallelGetMetadata(t, tasks, checks)
+
+ newTasks := make([]ExecTask, 0, len(tasks))
+
+ for _, t := range tasks {
+ temp := t.(*testExecTask)
+
+ keys := make([]int, len(allDeps[temp.txIdx]))
+
+ i := 0
+
+ for k := range allDeps[temp.txIdx] {
+ keys[i] = k
+ i++
+ }
+
+ temp.dependencies = keys
+ newTasks = append(newTasks, temp)
+ }
+
+ return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
+ }
+
+ testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIOs, taskRunner)
+}
+
+func TestZeroTx(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{0}
+ numReads := []int{20}
+ numWrites := []int{20}
+ numNonIO := []int{100}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration) {
+ sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(1))) }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ return runParallel(t, tasks, checks, false), serialDuration
+ }
+
+ testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestAlternatingTx(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{200}
+ numReads := []int{20}
+ numWrites := []int{20}
+ numNonIO := []int{100}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration) {
+ sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i % 2))) }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ return runParallel(t, tasks, checks, false), serialDuration
+ }
+
+ testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestAlternatingTxWithMetadata(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{200}
+ numReads := []int{20}
+ numWrites := []int{20}
+ numNonIO := []int{100}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
+ sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i % 2))) }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ parallelDuration := runParallel(t, tasks, checks, false)
+
+ allDeps := runParallelGetMetadata(t, tasks, checks)
+
+ newTasks := make([]ExecTask, 0, len(tasks))
+
+ for _, t := range tasks {
+ temp := t.(*testExecTask)
+
+ keys := make([]int, len(allDeps[temp.txIdx]))
+
+ i := 0
+
+ for k := range allDeps[temp.txIdx] {
+ keys[i] = k
+ i++
+ }
+
+ temp.dependencies = keys
+ newTasks = append(newTasks, temp)
+ }
+
+ return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
+ }
+
+ testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestMoreConflicts(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{10, 50, 100, 200, 300}
+ numReads := []int{20, 100, 200}
+ numWrites := []int{20, 100, 200}
+ numNonIO := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration) {
+ sender := func(i int) common.Address {
+ randomness := rand.Intn(10) + 10
+ return common.BigToAddress(big.NewInt(int64(i / randomness)))
+ }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ return runParallel(t, tasks, checks, false), serialDuration
+ }
+
+ testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestMoreConflictsWithMetadata(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{300}
+ numReads := []int{100, 200}
+ numWrites := []int{100, 200}
+ numNonIO := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
+ sender := func(i int) common.Address {
+ randomness := rand.Intn(10) + 10
+ return common.BigToAddress(big.NewInt(int64(i / randomness)))
+ }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ parallelDuration := runParallel(t, tasks, checks, false)
+
+ allDeps := runParallelGetMetadata(t, tasks, checks)
+
+ newTasks := make([]ExecTask, 0, len(tasks))
+
+ for _, t := range tasks {
+ temp := t.(*testExecTask)
+
+ keys := make([]int, len(allDeps[temp.txIdx]))
+
+ i := 0
+
+ for k := range allDeps[temp.txIdx] {
+ keys[i] = k
+ i++
+ }
+
+ temp.dependencies = keys
+ newTasks = append(newTasks, temp)
+ }
+
+ return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
+ }
+
+ testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestRandomTx(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{10, 50, 100, 200, 300}
+ numReads := []int{20, 100, 200}
+ numWrites := []int{20, 100, 200}
+ numNonIO := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration) {
+ // Randomly assign this tx to one of 10 senders
+ sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(rand.Intn(10)))) }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ return runParallel(t, tasks, checks, false), serialDuration
+ }
+
+ testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestRandomTxWithMetadata(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{300}
+ numReads := []int{100, 200}
+ numWrites := []int{100, 200}
+ numNonIO := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
+ // Randomly assign this tx to one of 10 senders
+ sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(rand.Intn(10)))) }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
+
+ parallelDuration := runParallel(t, tasks, checks, false)
+
+ allDeps := runParallelGetMetadata(t, tasks, checks)
+
+ newTasks := make([]ExecTask, 0, len(tasks))
+
+ for _, t := range tasks {
+ temp := t.(*testExecTask)
+
+ keys := make([]int, len(allDeps[temp.txIdx]))
+
+ i := 0
+
+ for k := range allDeps[temp.txIdx] {
+ keys[i] = k
+ i++
+ }
+
+ temp.dependencies = keys
+ newTasks = append(newTasks, temp)
+ }
+
+ return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
+ }
+
+ testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestTxWithLongTailRead(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{10, 50, 100, 200, 300}
+ numReads := []int{20, 100, 200}
+ numWrites := []int{20, 100, 200}
+ numNonIO := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration) {
+ sender := func(i int) common.Address {
+ randomness := rand.Intn(10) + 10
+ return common.BigToAddress(big.NewInt(int64(i / randomness)))
+ }
+
+ longTailReadTimer := longTailTimeGenerator(4*time.Microsecond, 12*time.Microsecond, 7, 10)
+
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, longTailReadTimer, writeTime, nonIOTime)
+
+ return runParallel(t, tasks, checks, false), serialDuration
+ }
+
+ testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestTxWithLongTailReadWithMetadata(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{300}
+ numReads := []int{100, 200}
+ numWrites := []int{100, 200}
+ numNonIO := []int{100, 500}
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
+ sender := func(i int) common.Address {
+ randomness := rand.Intn(10) + 10
+ return common.BigToAddress(big.NewInt(int64(i / randomness)))
+ }
+
+ longTailReadTimer := longTailTimeGenerator(4*time.Microsecond, 12*time.Microsecond, 7, 10)
+
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, longTailReadTimer, writeTime, nonIOTime)
+
+ parallelDuration := runParallel(t, tasks, checks, false)
+
+ allDeps := runParallelGetMetadata(t, tasks, checks)
+
+ newTasks := make([]ExecTask, 0, len(tasks))
+
+ for _, t := range tasks {
+ temp := t.(*testExecTask)
+
+ keys := make([]int, len(allDeps[temp.txIdx]))
+
+ i := 0
+
+ for k := range allDeps[temp.txIdx] {
+ keys[i] = k
+ i++
+ }
+
+ temp.dependencies = keys
+ newTasks = append(newTasks, temp)
+ }
+
+ return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
+ }
+
+ testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestDexScenario(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{10, 50, 100, 200, 300}
+ numReads := []int{20, 100, 200}
+ numWrites := []int{20, 100, 200}
+ numNonIO := []int{100, 500}
+
+ postValidation := func(pe *ParallelExecutor) error {
+ if pe.lastSettled == len(pe.tasks) {
+ for i, inputs := range pe.lastTxIO.inputs {
+ for _, input := range inputs {
+ if input.V.TxnIndex != i-1 {
+ return fmt.Errorf("Tx %d should depend on tx %d, but it actually depends on %d", i, i-1, input.V.TxnIndex)
+ }
+ }
+ }
+ }
+
+ return nil
+ }
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, postValidation, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration) {
+ sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i))) }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, dexPathGenerator, readTime, writeTime, nonIOTime)
+
+ return runParallel(t, tasks, checks, false), serialDuration
+ }
+
+ testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestDexScenarioWithMetadata(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ totalTxs := []int{300}
+ numReads := []int{100, 200}
+ numWrites := []int{100, 200}
+ numNonIO := []int{100, 500}
+
+ postValidation := func(pe *ParallelExecutor) error {
+ if pe.lastSettled == len(pe.tasks) {
+ for i, inputs := range pe.lastTxIO.inputs {
+ for _, input := range inputs {
+ if input.V.TxnIndex != i-1 {
+ return fmt.Errorf("Tx %d should depend on tx %d, but it actually depends on %d", i, i-1, input.V.TxnIndex)
+ }
+ }
+ }
+ }
+
+ return nil
+ }
+
+ checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, postValidation, checkNoDroppedTx})
+
+ taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
+ sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i))) }
+ tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, dexPathGenerator, readTime, writeTime, nonIOTime)
+
+ parallelDuration := runParallel(t, tasks, checks, false)
+
+ allDeps := runParallelGetMetadata(t, tasks, checks)
+
+ newTasks := make([]ExecTask, 0, len(tasks))
+
+ for _, t := range tasks {
+ temp := t.(*testExecTask)
+
+ keys := make([]int, len(allDeps[temp.txIdx]))
+
+ i := 0
+
+ for k := range allDeps[temp.txIdx] {
+ keys[i] = k
+ i++
+ }
+
+ temp.dependencies = keys
+ newTasks = append(newTasks, temp)
+ }
+
+ return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
+ }
+
+ testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
+}
+
+func TestBreakFromCircularDependency(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ tasks := make([]ExecTask, 5)
+
+ for i := range tasks {
+ tasks[i] = &testExecTask{
+ txIdx: i,
+ dependencies: []int{
+ (i + len(tasks) - 1) % len(tasks),
+ },
+ }
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ // This should not hang
+ _, err := ExecuteParallel(tasks, false, true, ctx)
+
+ if err == nil {
+ t.Error("Expected cancel error")
+ }
+}
+
+func TestBreakFromPartialCircularDependency(t *testing.T) {
+ t.Parallel()
+ rand.Seed(0)
+
+ tasks := make([]ExecTask, 5)
+
+ for i := range tasks {
+ if i < 3 {
+ tasks[i] = &testExecTask{
+ txIdx: i,
+ dependencies: []int{
+ (i + 2) % 3,
+ },
+ }
+ } else {
+ tasks[i] = &testExecTask{
+ txIdx: i,
+ dependencies: []int{},
+ }
+ }
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ // This should not hang
+ _, err := ExecuteParallel(tasks, false, true, ctx)
+
+ if err == nil {
+ t.Error("Expected cancel error")
+ }
+}
diff --git a/core/blockstm/mvhashmap.go b/core/blockstm/mvhashmap.go
new file mode 100644
index 0000000000..2a517bcc84
--- /dev/null
+++ b/core/blockstm/mvhashmap.go
@@ -0,0 +1,290 @@
+package blockstm
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/emirpasic/gods/maps/treemap"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+const FlagDone = 0
+const FlagEstimate = 1
+
+const addressType = 1
+const stateType = 2
+const subpathType = 3
+
+const KeyLength = common.AddressLength + common.HashLength + 2
+
+type Key [KeyLength]byte
+
+func (k Key) IsAddress() bool {
+ return k[KeyLength-1] == addressType
+}
+
+func (k Key) IsState() bool {
+ return k[KeyLength-1] == stateType
+}
+
+func (k Key) IsSubpath() bool {
+ return k[KeyLength-1] == subpathType
+}
+
+func (k Key) GetAddress() common.Address {
+ return common.BytesToAddress(k[:common.AddressLength])
+}
+
+func (k Key) GetStateKey() common.Hash {
+ return common.BytesToHash(k[common.AddressLength : KeyLength-2])
+}
+
+func (k Key) GetSubpath() byte {
+ return k[KeyLength-2]
+}
+
+func newKey(addr common.Address, hash common.Hash, subpath byte, keyType byte) Key {
+ var k Key
+
+ copy(k[:common.AddressLength], addr.Bytes())
+ copy(k[common.AddressLength:KeyLength-2], hash.Bytes())
+ k[KeyLength-2] = subpath
+ k[KeyLength-1] = keyType
+
+ return k
+}
+
+func NewAddressKey(addr common.Address) Key {
+ return newKey(addr, common.Hash{}, 0, addressType)
+}
+
+func NewStateKey(addr common.Address, hash common.Hash) Key {
+ k := newKey(addr, hash, 0, stateType)
+ if !k.IsState() {
+ panic(fmt.Errorf("key is not a state key"))
+ }
+
+ return k
+}
+
+func NewSubpathKey(addr common.Address, subpath byte) Key {
+ return newKey(addr, common.Hash{}, subpath, subpathType)
+}
+
+type MVHashMap struct {
+ m sync.Map
+ s sync.Map
+}
+
+func MakeMVHashMap() *MVHashMap {
+ return &MVHashMap{}
+}
+
+type WriteCell struct {
+ flag uint
+ incarnation int
+ data interface{}
+}
+
+type TxnIndexCells struct {
+ rw sync.RWMutex
+ tm *treemap.Map
+}
+
+type Version struct {
+ TxnIndex int
+ Incarnation int
+}
+
+func (mv *MVHashMap) getKeyCells(k Key, fNoKey func(kenc Key) *TxnIndexCells) (cells *TxnIndexCells) {
+ val, ok := mv.m.Load(k)
+
+ if !ok {
+ cells = fNoKey(k)
+ } else {
+ cells = val.(*TxnIndexCells)
+ }
+
+ return
+}
+
+func (mv *MVHashMap) Write(k Key, v Version, data interface{}) {
+ cells := mv.getKeyCells(k, func(kenc Key) (cells *TxnIndexCells) {
+ n := &TxnIndexCells{
+ rw: sync.RWMutex{},
+ tm: treemap.NewWithIntComparator(),
+ }
+ cells = n
+ val, _ := mv.m.LoadOrStore(kenc, n)
+ cells = val.(*TxnIndexCells)
+ return
+ })
+
+ cells.rw.RLock()
+ ci, ok := cells.tm.Get(v.TxnIndex)
+ cells.rw.RUnlock()
+
+ if ok {
+ if ci.(*WriteCell).incarnation > v.Incarnation {
+ panic(fmt.Errorf("existing transaction value does not have lower incarnation: %v, %v",
+ k, v.TxnIndex))
+ }
+
+ ci.(*WriteCell).flag = FlagDone
+ ci.(*WriteCell).incarnation = v.Incarnation
+ ci.(*WriteCell).data = data
+ } else {
+ cells.rw.Lock()
+ if ci, ok = cells.tm.Get(v.TxnIndex); !ok {
+ cells.tm.Put(v.TxnIndex, &WriteCell{
+ flag: FlagDone,
+ incarnation: v.Incarnation,
+ data: data,
+ })
+ } else {
+ ci.(*WriteCell).flag = FlagDone
+ ci.(*WriteCell).incarnation = v.Incarnation
+ ci.(*WriteCell).data = data
+ }
+ cells.rw.Unlock()
+ }
+}
+
+func (mv *MVHashMap) ReadStorage(k Key, fallBack func() any) any {
+ data, ok := mv.s.Load(string(k[:]))
+ if !ok {
+ data = fallBack()
+ data, _ = mv.s.LoadOrStore(string(k[:]), data)
+ }
+
+ return data
+}
+
+func (mv *MVHashMap) MarkEstimate(k Key, txIdx int) {
+ cells := mv.getKeyCells(k, func(_ Key) *TxnIndexCells {
+ panic(fmt.Errorf("path must already exist"))
+ })
+
+ cells.rw.RLock()
+ if ci, ok := cells.tm.Get(txIdx); !ok {
+ panic(fmt.Sprintf("should not happen - cell should be present for path. TxIdx: %v, path, %x, cells keys: %v", txIdx, k, cells.tm.Keys()))
+ } else {
+ ci.(*WriteCell).flag = FlagEstimate
+ }
+ cells.rw.RUnlock()
+}
+
+func (mv *MVHashMap) Delete(k Key, txIdx int) {
+ cells := mv.getKeyCells(k, func(_ Key) *TxnIndexCells {
+ panic(fmt.Errorf("path must already exist"))
+ })
+
+ cells.rw.Lock()
+ defer cells.rw.Unlock()
+ cells.tm.Remove(txIdx)
+}
+
+const (
+ MVReadResultDone = 0
+ MVReadResultDependency = 1
+ MVReadResultNone = 2
+)
+
+type MVReadResult struct {
+ depIdx int
+ incarnation int
+ value interface{}
+}
+
+func (res *MVReadResult) DepIdx() int {
+ return res.depIdx
+}
+
+func (res *MVReadResult) Incarnation() int {
+ return res.incarnation
+}
+
+func (res *MVReadResult) Value() interface{} {
+ return res.value
+}
+
+func (mvr MVReadResult) Status() int {
+ if mvr.depIdx != -1 {
+ if mvr.incarnation == -1 {
+ return MVReadResultDependency
+ } else {
+ return MVReadResultDone
+ }
+ }
+
+ return MVReadResultNone
+}
+
+func (mv *MVHashMap) Read(k Key, txIdx int) (res MVReadResult) {
+ res.depIdx = -1
+ res.incarnation = -1
+
+ cells := mv.getKeyCells(k, func(_ Key) *TxnIndexCells {
+ return nil
+ })
+ if cells == nil {
+ return
+ }
+
+ cells.rw.RLock()
+ fk, fv := cells.tm.Floor(txIdx - 1)
+ cells.rw.RUnlock()
+
+ if fk != nil && fv != nil {
+ c := fv.(*WriteCell)
+ switch c.flag {
+ case FlagEstimate:
+ res.depIdx = fk.(int)
+ res.value = c.data
+ case FlagDone:
+ {
+ res.depIdx = fk.(int)
+ res.incarnation = c.incarnation
+ res.value = c.data
+ }
+ default:
+ panic(fmt.Errorf("should not happen - unknown flag value"))
+ }
+ }
+
+ return
+}
+
+func (mv *MVHashMap) FlushMVWriteSet(writes []WriteDescriptor) {
+ for _, v := range writes {
+ mv.Write(v.Path, v.V, v.Val)
+ }
+}
+
+func ValidateVersion(txIdx int, lastInputOutput *TxnInputOutput, versionedData *MVHashMap) (valid bool) {
+ valid = true
+
+ for _, rd := range lastInputOutput.ReadSet(txIdx) {
+ mvResult := versionedData.Read(rd.Path, txIdx)
+ switch mvResult.Status() {
+ case MVReadResultDone:
+ valid = rd.Kind == ReadKindMap && rd.V == Version{
+ TxnIndex: mvResult.depIdx,
+ Incarnation: mvResult.incarnation,
+ }
+ case MVReadResultDependency:
+ valid = false
+ case MVReadResultNone:
+ valid = rd.Kind == ReadKindStorage // feels like an assertion?
+ default:
+ panic(fmt.Errorf("should not happen - undefined mv read status: %ver", mvResult.Status()))
+ }
+
+ if !valid {
+ break
+ }
+ }
+
+ return
+}
diff --git a/core/blockstm/mvhashmap_test.go b/core/blockstm/mvhashmap_test.go
new file mode 100644
index 0000000000..7ed728426c
--- /dev/null
+++ b/core/blockstm/mvhashmap_test.go
@@ -0,0 +1,344 @@
+package blockstm
+
+import (
+ "fmt"
+ "math/big"
+ "math/rand"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var randomness = rand.Intn(10) + 10
+
+// create test data for a given txIdx and incarnation
+func valueFor(txIdx, inc int) []byte {
+ return []byte(fmt.Sprintf("%ver:%ver:%ver", txIdx*5, txIdx+inc, inc*5))
+}
+
+func getCommonAddress(i int) common.Address {
+ return common.BigToAddress(big.NewInt(int64(i % randomness)))
+}
+
+func TestHelperFunctions(t *testing.T) {
+ t.Parallel()
+
+ ap1 := NewAddressKey(getCommonAddress(1))
+ ap2 := NewAddressKey(getCommonAddress(2))
+
+ mvh := MakeMVHashMap()
+
+ mvh.Write(ap1, Version{0, 1}, valueFor(0, 1))
+ mvh.Write(ap1, Version{0, 2}, valueFor(0, 2))
+ res := mvh.Read(ap1, 0)
+ require.Equal(t, -1, res.DepIdx())
+ require.Equal(t, -1, res.Incarnation())
+ require.Equal(t, 2, res.Status())
+
+ mvh.Write(ap2, Version{1, 1}, valueFor(1, 1))
+ mvh.Write(ap2, Version{1, 2}, valueFor(1, 2))
+ res = mvh.Read(ap2, 1)
+ require.Equal(t, -1, res.DepIdx())
+ require.Equal(t, -1, res.Incarnation())
+ require.Equal(t, 2, res.Status())
+
+ mvh.Write(ap1, Version{2, 1}, valueFor(2, 1))
+ mvh.Write(ap1, Version{2, 2}, valueFor(2, 2))
+ res = mvh.Read(ap1, 2)
+ require.Equal(t, 0, res.DepIdx())
+ require.Equal(t, 2, res.Incarnation())
+ require.Equal(t, valueFor(0, 2), res.Value().([]byte))
+ require.Equal(t, 0, res.Status())
+}
+
+func TestFlushMVWrite(t *testing.T) {
+ t.Parallel()
+
+ ap1 := NewAddressKey(getCommonAddress(1))
+ ap2 := NewAddressKey(getCommonAddress(2))
+
+ mvh := MakeMVHashMap()
+
+ var res MVReadResult
+
+ wd := []WriteDescriptor{}
+
+ wd = append(wd, WriteDescriptor{
+ Path: ap1,
+ V: Version{0, 1},
+ Val: valueFor(0, 1),
+ })
+ wd = append(wd, WriteDescriptor{
+ Path: ap1,
+ V: Version{0, 2},
+ Val: valueFor(0, 2),
+ })
+ wd = append(wd, WriteDescriptor{
+ Path: ap2,
+ V: Version{1, 1},
+ Val: valueFor(1, 1),
+ })
+ wd = append(wd, WriteDescriptor{
+ Path: ap2,
+ V: Version{1, 2},
+ Val: valueFor(1, 2),
+ })
+ wd = append(wd, WriteDescriptor{
+ Path: ap1,
+ V: Version{2, 1},
+ Val: valueFor(2, 1),
+ })
+ wd = append(wd, WriteDescriptor{
+ Path: ap1,
+ V: Version{2, 2},
+ Val: valueFor(2, 2),
+ })
+
+ mvh.FlushMVWriteSet(wd)
+
+ res = mvh.Read(ap1, 0)
+ require.Equal(t, -1, res.DepIdx())
+ require.Equal(t, -1, res.Incarnation())
+ require.Equal(t, 2, res.Status())
+
+ res = mvh.Read(ap2, 1)
+ require.Equal(t, -1, res.DepIdx())
+ require.Equal(t, -1, res.Incarnation())
+ require.Equal(t, 2, res.Status())
+
+ res = mvh.Read(ap1, 2)
+ require.Equal(t, 0, res.DepIdx())
+ require.Equal(t, 2, res.Incarnation())
+ require.Equal(t, valueFor(0, 2), res.Value().([]byte))
+ require.Equal(t, 0, res.Status())
+}
+
+// TODO - handle panic
+func TestLowerIncarnation(t *testing.T) {
+ t.Parallel()
+
+ ap1 := NewAddressKey(getCommonAddress(1))
+
+ mvh := MakeMVHashMap()
+
+ mvh.Write(ap1, Version{0, 2}, valueFor(0, 2))
+ mvh.Read(ap1, 0)
+ mvh.Write(ap1, Version{1, 2}, valueFor(1, 2))
+ mvh.Write(ap1, Version{0, 5}, valueFor(0, 5))
+ mvh.Write(ap1, Version{1, 5}, valueFor(1, 5))
+}
+
+func TestMarkEstimate(t *testing.T) {
+ t.Parallel()
+
+ ap1 := NewAddressKey(getCommonAddress(1))
+
+ mvh := MakeMVHashMap()
+
+ mvh.Write(ap1, Version{7, 2}, valueFor(7, 2))
+ mvh.MarkEstimate(ap1, 7)
+ mvh.Write(ap1, Version{7, 4}, valueFor(7, 4))
+}
+
+func TestMVHashMapBasics(t *testing.T) {
+ t.Parallel()
+
+ // memory locations
+ ap1 := NewAddressKey(getCommonAddress(1))
+ ap2 := NewAddressKey(getCommonAddress(2))
+ ap3 := NewAddressKey(getCommonAddress(3))
+
+ mvh := MakeMVHashMap()
+
+ res := mvh.Read(ap1, 5)
+ require.Equal(t, -1, res.depIdx)
+
+ mvh.Write(ap1, Version{10, 1}, valueFor(10, 1))
+
+ res = mvh.Read(ap1, 9)
+ require.Equal(t, -1, res.depIdx, "reads that should go the the DB return dependency -1")
+ res = mvh.Read(ap1, 10)
+ require.Equal(t, -1, res.depIdx, "Read returns entries from smaller txns, not txn 10")
+
+ // Reads for a higher txn return the entry written by txn 10.
+ res = mvh.Read(ap1, 15)
+ require.Equal(t, 10, res.depIdx, "reads for a higher txn return the entry written by txn 10.")
+ require.Equal(t, 1, res.incarnation)
+ require.Equal(t, valueFor(10, 1), res.value)
+
+ // More writes.
+ mvh.Write(ap1, Version{12, 0}, valueFor(12, 0))
+ mvh.Write(ap1, Version{8, 3}, valueFor(8, 3))
+
+ // Verify reads.
+ res = mvh.Read(ap1, 15)
+ require.Equal(t, 12, res.depIdx)
+ require.Equal(t, 0, res.incarnation)
+ require.Equal(t, valueFor(12, 0), res.value)
+
+ res = mvh.Read(ap1, 11)
+ require.Equal(t, 10, res.depIdx)
+ require.Equal(t, 1, res.incarnation)
+ require.Equal(t, valueFor(10, 1), res.value)
+
+ res = mvh.Read(ap1, 10)
+ require.Equal(t, 8, res.depIdx)
+ require.Equal(t, 3, res.incarnation)
+ require.Equal(t, valueFor(8, 3), res.value)
+
+ // Mark the entry written by 10 as an estimate.
+ mvh.MarkEstimate(ap1, 10)
+
+ res = mvh.Read(ap1, 11)
+ require.Equal(t, 10, res.depIdx)
+ require.Equal(t, -1, res.incarnation, "dep at tx 10 is now an estimate")
+
+ // Delete the entry written by 10, write to a different ap.
+ mvh.Delete(ap1, 10)
+ mvh.Write(ap2, Version{10, 2}, valueFor(10, 2))
+
+ // Read by txn 11 no longer observes entry from txn 10.
+ res = mvh.Read(ap1, 11)
+ require.Equal(t, 8, res.depIdx)
+ require.Equal(t, 3, res.incarnation)
+ require.Equal(t, valueFor(8, 3), res.value)
+
+ // Reads, writes for ap2 and ap3.
+ mvh.Write(ap2, Version{5, 0}, valueFor(5, 0))
+ mvh.Write(ap3, Version{20, 4}, valueFor(20, 4))
+
+ res = mvh.Read(ap2, 10)
+ require.Equal(t, 5, res.depIdx)
+ require.Equal(t, 0, res.incarnation)
+ require.Equal(t, valueFor(5, 0), res.value)
+
+ res = mvh.Read(ap3, 21)
+ require.Equal(t, 20, res.depIdx)
+ require.Equal(t, 4, res.incarnation)
+ require.Equal(t, valueFor(20, 4), res.value)
+
+ // Clear ap1 and ap3.
+ mvh.Delete(ap1, 12)
+ mvh.Delete(ap1, 8)
+ mvh.Delete(ap3, 20)
+
+ // Reads from ap1 and ap3 go to db.
+ res = mvh.Read(ap1, 30)
+ require.Equal(t, -1, res.depIdx)
+
+ res = mvh.Read(ap3, 30)
+ require.Equal(t, -1, res.depIdx)
+
+ // No-op delete at ap2 - doesn't panic because ap2 does exist
+ mvh.Delete(ap2, 11)
+
+ // Read entry by txn 10 at ap2.
+ res = mvh.Read(ap2, 15)
+ require.Equal(t, 10, res.depIdx)
+ require.Equal(t, 2, res.incarnation)
+ require.Equal(t, valueFor(10, 2), res.value)
+}
+
+func BenchmarkWriteTimeSameLocationDifferentTxIdx(b *testing.B) {
+ mvh2 := MakeMVHashMap()
+ ap2 := NewAddressKey(getCommonAddress(2))
+
+ randInts := []int{}
+ for i := 0; i < b.N; i++ {
+ randInts = append(randInts, rand.Intn(1000000000000000))
+ }
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ mvh2.Write(ap2, Version{randInts[i], 1}, valueFor(randInts[i], 1))
+ }
+}
+
+func BenchmarkReadTimeSameLocationDifferentTxIdx(b *testing.B) {
+ mvh2 := MakeMVHashMap()
+ ap2 := NewAddressKey(getCommonAddress(2))
+ txIdxSlice := []int{}
+
+ for i := 0; i < b.N; i++ {
+ txIdx := rand.Intn(1000000000000000)
+ txIdxSlice = append(txIdxSlice, txIdx)
+ mvh2.Write(ap2, Version{txIdx, 1}, valueFor(txIdx, 1))
+ }
+
+ b.ResetTimer()
+
+ for _, value := range txIdxSlice {
+ mvh2.Read(ap2, value)
+ }
+}
+
+func TestTimeComplexity(t *testing.T) {
+ t.Parallel()
+
+ // for 1000000 read and write with no dependency at different memory location
+ mvh1 := MakeMVHashMap()
+
+ for i := 0; i < 1000000; i++ {
+ ap1 := NewAddressKey(getCommonAddress(i))
+ mvh1.Write(ap1, Version{i, 1}, valueFor(i, 1))
+ mvh1.Read(ap1, i)
+ }
+
+ // for 1000000 read and write with dependency at same memory location
+ mvh2 := MakeMVHashMap()
+ ap2 := NewAddressKey(getCommonAddress(2))
+
+ for i := 0; i < 1000000; i++ {
+ mvh2.Write(ap2, Version{i, 1}, valueFor(i, 1))
+ mvh2.Read(ap2, i)
+ }
+}
+
+func TestWriteTimeSameLocationDifferentTxnIdx(t *testing.T) {
+ t.Parallel()
+
+ mvh1 := MakeMVHashMap()
+ ap1 := NewAddressKey(getCommonAddress(1))
+
+ for i := 0; i < 1000000; i++ {
+ mvh1.Write(ap1, Version{i, 1}, valueFor(i, 1))
+ }
+}
+
+func TestWriteTimeSameLocationSameTxnIdx(t *testing.T) {
+ t.Parallel()
+
+ mvh1 := MakeMVHashMap()
+ ap1 := NewAddressKey(getCommonAddress(1))
+
+ for i := 0; i < 1000000; i++ {
+ mvh1.Write(ap1, Version{1, i}, valueFor(i, 1))
+ }
+}
+
+func TestWriteTimeDifferentLocation(t *testing.T) {
+ t.Parallel()
+
+ mvh1 := MakeMVHashMap()
+
+ for i := 0; i < 1000000; i++ {
+ ap1 := NewAddressKey(getCommonAddress(i))
+ mvh1.Write(ap1, Version{i, 1}, valueFor(i, 1))
+ }
+}
+
+func TestReadTimeSameLocation(t *testing.T) {
+ t.Parallel()
+
+ mvh1 := MakeMVHashMap()
+ ap1 := NewAddressKey(getCommonAddress(1))
+
+ mvh1.Write(ap1, Version{1, 1}, valueFor(1, 1))
+
+ for i := 0; i < 1000000; i++ {
+ mvh1.Read(ap1, 2)
+ }
+}
diff --git a/core/blockstm/status.go b/core/blockstm/status.go
new file mode 100644
index 0000000000..3025cf6c3e
--- /dev/null
+++ b/core/blockstm/status.go
@@ -0,0 +1,225 @@
+package blockstm
+
+import (
+ "fmt"
+ "sort"
+)
+
+func makeStatusManager(numTasks int) (t taskStatusManager) {
+ t.pending = make([]int, numTasks)
+ for i := 0; i < numTasks; i++ {
+ t.pending[i] = i
+ }
+
+ t.dependency = make(map[int]map[int]bool, numTasks)
+ t.blocker = make(map[int]map[int]bool, numTasks)
+
+ for i := 0; i < numTasks; i++ {
+ t.blocker[i] = make(map[int]bool)
+ }
+
+ return
+}
+
+type taskStatusManager struct {
+ pending []int
+ inProgress []int
+ complete []int
+ dependency map[int]map[int]bool
+ blocker map[int]map[int]bool
+}
+
+func insertInList(l []int, v int) []int {
+ if len(l) == 0 || v > l[len(l)-1] {
+ return append(l, v)
+ } else {
+ x := sort.SearchInts(l, v)
+ if x < len(l) && l[x] == v {
+ // already in list
+ return l
+ }
+ a := append(l[:x+1], l[x:]...)
+ a[x] = v
+ return a
+ }
+}
+
+func (m *taskStatusManager) takeNextPending() int {
+ if len(m.pending) == 0 {
+ return -1
+ }
+
+ x := m.pending[0]
+ m.pending = m.pending[1:]
+ m.inProgress = insertInList(m.inProgress, x)
+
+ return x
+}
+
+func hasNoGap(l []int) bool {
+ return l[0]+len(l) == l[len(l)-1]+1
+}
+
+func (m taskStatusManager) maxAllComplete() int {
+ if len(m.complete) == 0 || m.complete[0] != 0 {
+ return -1
+ } else if m.complete[len(m.complete)-1] == len(m.complete)-1 {
+ return m.complete[len(m.complete)-1]
+ } else {
+ for i := len(m.complete) - 2; i >= 0; i-- {
+ if hasNoGap(m.complete[:i+1]) {
+ return m.complete[i]
+ }
+ }
+ }
+
+ return -1
+}
+
+func (m *taskStatusManager) pushPending(tx int) {
+ m.pending = insertInList(m.pending, tx)
+}
+
+func removeFromList(l []int, v int, expect bool) []int {
+ x := sort.SearchInts(l, v)
+ if x == -1 || l[x] != v {
+ if expect {
+ panic(fmt.Errorf("should not happen - element expected in list"))
+ }
+
+ return l
+ }
+
+ switch x {
+ case 0:
+ return l[1:]
+ case len(l) - 1:
+ return l[:len(l)-1]
+ default:
+ return append(l[:x], l[x+1:]...)
+ }
+}
+
+func (m *taskStatusManager) markComplete(tx int) {
+ m.inProgress = removeFromList(m.inProgress, tx, true)
+ m.complete = insertInList(m.complete, tx)
+}
+
+func (m *taskStatusManager) minPending() int {
+ if len(m.pending) == 0 {
+ return -1
+ } else {
+ return m.pending[0]
+ }
+}
+
+func (m *taskStatusManager) countComplete() int {
+ return len(m.complete)
+}
+
+func (m *taskStatusManager) addDependencies(blocker int, dependent int) bool {
+ if blocker < 0 || blocker >= dependent {
+ return false
+ }
+
+ curblockers := m.blocker[dependent]
+
+ if m.checkComplete(blocker) {
+ // Blocker has already completed
+ delete(curblockers, blocker)
+
+ return len(curblockers) > 0
+ }
+
+ if _, ok := m.dependency[blocker]; !ok {
+ m.dependency[blocker] = make(map[int]bool)
+ }
+
+ m.dependency[blocker][dependent] = true
+ curblockers[blocker] = true
+
+ return true
+}
+
+func (m *taskStatusManager) isBlocked(tx int) bool {
+ return len(m.blocker[tx]) > 0
+}
+
+func (m *taskStatusManager) removeDependency(tx int) {
+ if deps, ok := m.dependency[tx]; ok && len(deps) > 0 {
+ for k := range deps {
+ delete(m.blocker[k], tx)
+
+ if len(m.blocker[k]) == 0 {
+ if !m.checkComplete(k) && !m.checkPending(k) && !m.checkInProgress(k) {
+ m.pushPending(k)
+ }
+ }
+ }
+
+ delete(m.dependency, tx)
+ }
+}
+
+func (m *taskStatusManager) clearInProgress(tx int) {
+ m.inProgress = removeFromList(m.inProgress, tx, true)
+}
+
+func (m *taskStatusManager) checkInProgress(tx int) bool {
+ x := sort.SearchInts(m.inProgress, tx)
+ if x < len(m.inProgress) && m.inProgress[x] == tx {
+ return true
+ }
+
+ return false
+}
+
+func (m *taskStatusManager) checkPending(tx int) bool {
+ x := sort.SearchInts(m.pending, tx)
+ if x < len(m.pending) && m.pending[x] == tx {
+ return true
+ }
+
+ return false
+}
+
+func (m *taskStatusManager) checkComplete(tx int) bool {
+ x := sort.SearchInts(m.complete, tx)
+ if x < len(m.complete) && m.complete[x] == tx {
+ return true
+ }
+
+ return false
+}
+
+// getRevalidationRange: this range will be all tasks from tx (inclusive) that are not currently in progress up to the
+//
+// 'all complete' limit
+func (m *taskStatusManager) getRevalidationRange(txFrom int) (ret []int) {
+ max := m.maxAllComplete() // haven't learned to trust compilers :)
+ for x := txFrom; x <= max; x++ {
+ if !m.checkInProgress(x) {
+ ret = append(ret, x)
+ }
+ }
+
+ return
+}
+
+func (m *taskStatusManager) pushPendingSet(set []int) {
+ for _, v := range set {
+ if m.checkComplete(v) {
+ m.clearComplete(v)
+ }
+
+ m.pushPending(v)
+ }
+}
+
+func (m *taskStatusManager) clearComplete(tx int) {
+ m.complete = removeFromList(m.complete, tx, false)
+}
+
+func (m *taskStatusManager) clearPending(tx int) {
+ m.pending = removeFromList(m.pending, tx, false)
+}
diff --git a/core/blockstm/status_test.go b/core/blockstm/status_test.go
new file mode 100644
index 0000000000..aff00d9a2f
--- /dev/null
+++ b/core/blockstm/status_test.go
@@ -0,0 +1,82 @@
+package blockstm
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestStatusBasics(t *testing.T) {
+ t.Parallel()
+
+ s := makeStatusManager(10)
+
+ x := s.takeNextPending()
+ require.Equal(t, 0, x)
+ require.True(t, s.checkInProgress(x))
+
+ x = s.takeNextPending()
+ require.Equal(t, 1, x)
+ require.True(t, s.checkInProgress(x))
+
+ x = s.takeNextPending()
+ require.Equal(t, 2, x)
+ require.True(t, s.checkInProgress(x))
+
+ s.markComplete(0)
+ require.False(t, s.checkInProgress(0))
+ s.markComplete(1)
+ s.markComplete(2)
+ require.False(t, s.checkInProgress(1))
+ require.False(t, s.checkInProgress(2))
+ require.Equal(t, 2, s.maxAllComplete())
+
+ x = s.takeNextPending()
+ require.Equal(t, 3, x)
+
+ x = s.takeNextPending()
+ require.Equal(t, 4, x)
+
+ s.markComplete(x)
+ require.False(t, s.checkInProgress(4))
+ require.Equal(t, 2, s.maxAllComplete(), "zero should still be min complete")
+
+ exp := []int{1, 2}
+ require.Equal(t, exp, s.getRevalidationRange(1))
+}
+
+func TestMaxComplete(t *testing.T) {
+ t.Parallel()
+
+ s := makeStatusManager(10)
+
+ for {
+ tx := s.takeNextPending()
+
+ if tx == -1 {
+ break
+ }
+
+ if tx != 7 {
+ s.markComplete(tx)
+ }
+ }
+
+ require.Equal(t, 6, s.maxAllComplete())
+
+ s2 := makeStatusManager(10)
+
+ for {
+ tx := s2.takeNextPending()
+
+ if tx == -1 {
+ break
+ }
+ }
+ s2.markComplete(2)
+ s2.markComplete(4)
+ require.Equal(t, -1, s2.maxAllComplete())
+
+ s2.complete = insertInList(s2.complete, 4)
+ require.Equal(t, 2, s2.countComplete())
+}
diff --git a/core/blockstm/txio.go b/core/blockstm/txio.go
new file mode 100644
index 0000000000..19955fb152
--- /dev/null
+++ b/core/blockstm/txio.go
@@ -0,0 +1,106 @@
+package blockstm
+
+const (
+ ReadKindMap = 0
+ ReadKindStorage = 1
+)
+
+type ReadDescriptor struct {
+ Path Key
+ Kind int
+ V Version
+}
+
+type WriteDescriptor struct {
+ Path Key
+ V Version
+ Val interface{}
+}
+
+type TxnInput []ReadDescriptor
+type TxnOutput []WriteDescriptor
+
+// hasNewWrite: returns true if the current set has a new write compared to the input
+func (txo TxnOutput) hasNewWrite(cmpSet []WriteDescriptor) bool {
+ if len(txo) == 0 {
+ return false
+ } else if len(cmpSet) == 0 || len(txo) > len(cmpSet) {
+ return true
+ }
+
+ cmpMap := map[Key]bool{cmpSet[0].Path: true}
+
+ for i := 1; i < len(cmpSet); i++ {
+ cmpMap[cmpSet[i].Path] = true
+ }
+
+ for _, v := range txo {
+ if !cmpMap[v.Path] {
+ return true
+ }
+ }
+
+ return false
+}
+
+type TxnInputOutput struct {
+ inputs []TxnInput
+ outputs []TxnOutput // write sets that should be checked during validation
+ outputsSet []map[Key]struct{}
+ allOutputs []TxnOutput // entire write sets in MVHashMap. allOutputs should always be a parent set of outputs
+}
+
+func (io *TxnInputOutput) ReadSet(txnIdx int) []ReadDescriptor {
+ return io.inputs[txnIdx]
+}
+
+func (io *TxnInputOutput) WriteSet(txnIdx int) []WriteDescriptor {
+ return io.outputs[txnIdx]
+}
+
+func (io *TxnInputOutput) AllWriteSet(txnIdx int) []WriteDescriptor {
+ return io.allOutputs[txnIdx]
+}
+
+func (io *TxnInputOutput) HasWritten(txnIdx int, k Key) bool {
+ _, ok := io.outputsSet[txnIdx][k]
+ return ok
+}
+
+func MakeTxnInputOutput(numTx int) *TxnInputOutput {
+ return &TxnInputOutput{
+ inputs: make([]TxnInput, numTx),
+ outputs: make([]TxnOutput, numTx),
+ outputsSet: make([]map[Key]struct{}, numTx),
+ allOutputs: make([]TxnOutput, numTx),
+ }
+}
+
+func (io *TxnInputOutput) recordRead(txId int, input []ReadDescriptor) {
+ io.inputs[txId] = input
+}
+
+func (io *TxnInputOutput) recordWrite(txId int, output []WriteDescriptor) {
+ io.outputs[txId] = output
+ io.outputsSet[txId] = make(map[Key]struct{}, len(output))
+
+ for _, v := range output {
+ io.outputsSet[txId][v.Path] = struct{}{}
+ }
+}
+
+func (io *TxnInputOutput) recordAllWrite(txId int, output []WriteDescriptor) {
+ io.allOutputs[txId] = output
+}
+
+func (io *TxnInputOutput) RecordReadAtOnce(inputs [][]ReadDescriptor) {
+ for ind, val := range inputs {
+ io.inputs[ind] = val
+ }
+}
+
+func (io *TxnInputOutput) RecordAllWriteAtOnce(outputs [][]WriteDescriptor) {
+ for ind, val := range outputs {
+ io.allOutputs[ind] = val
+ }
+}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index 0828a9ec7c..85fe8648e9 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -434,6 +434,19 @@ func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine,
return db, blocks
}
+// makeBlockChain creates a deterministic chain of blocks rooted at parent with fake invalid transactions.
+func makeFakeNonEmptyBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int, numTx int) []*types.Block {
+ blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
+ addr := common.Address{0: byte(seed), 19: byte(i)}
+ b.SetCoinbase(addr)
+ for j := 0; j < numTx; j++ {
+ b.txs = append(b.txs, types.NewTransaction(0, addr, big.NewInt(1000), params.TxGas, nil, nil))
+ }
+ })
+
+ return blocks
+}
+
type fakeChainReader struct {
config *params.ChainConfig
stateSyncData []*types.StateSyncData
diff --git a/core/evm.go b/core/evm.go
index f62106b179..979bcf1716 100644
--- a/core/evm.go
+++ b/core/evm.go
@@ -18,6 +18,7 @@ package core
import (
"math/big"
+ "sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
@@ -83,12 +84,18 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash
// Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...]
var cache []common.Hash
+ cacheMutex := &sync.Mutex{}
+
return func(n uint64) common.Hash {
if ref.Number.Uint64() <= n {
// This situation can happen if we're doing tracing and using
// block overrides.
return common.Hash{}
}
+
+ cacheMutex.Lock()
+ defer cacheMutex.Unlock()
+
// If there's no hash cache yet, make one
if len(cache) == 0 {
cache = append(cache, ref.ParentHash)
diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go
new file mode 100644
index 0000000000..8956f6f4d2
--- /dev/null
+++ b/core/parallel_state_processor.go
@@ -0,0 +1,429 @@
+// Copyright 2015 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 core
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/misc"
+ "github.com/ethereum/go-ethereum/core/blockstm"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/metrics"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+type ParallelEVMConfig struct {
+ Enable bool
+ SpeculativeProcesses int
+}
+
+// StateProcessor is a basic Processor, which takes care of transitioning
+// state from one point to another.
+//
+// StateProcessor implements Processor.
+type ParallelStateProcessor struct {
+ config *params.ChainConfig // Chain configuration options
+ bc *BlockChain // Canonical block chain
+ engine consensus.Engine // Consensus engine used for block rewards
+}
+
+// NewStateProcessor initialises a new StateProcessor.
+func NewParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *ParallelStateProcessor {
+ return &ParallelStateProcessor{
+ config: config,
+ bc: bc,
+ engine: engine,
+ }
+}
+
+type ExecutionTask struct {
+ msg types.Message
+ config *params.ChainConfig
+
+ gasLimit uint64
+ blockNumber *big.Int
+ blockHash common.Hash
+ tx *types.Transaction
+ index int
+ statedb *state.StateDB // State database that stores the modified values after tx execution.
+ cleanStateDB *state.StateDB // A clean copy of the initial statedb. It should not be modified.
+ finalStateDB *state.StateDB // The final statedb.
+ header *types.Header
+ blockChain *BlockChain
+ evmConfig vm.Config
+ result *ExecutionResult
+ shouldDelayFeeCal *bool
+ shouldRerunWithoutFeeDelay bool
+ sender common.Address
+ totalUsedGas *uint64
+ receipts *types.Receipts
+ allLogs *[]*types.Log
+
+ // length of dependencies -> 2 + k (k = a whole number)
+ // first 2 element in dependencies -> transaction index, and flag representing if delay is allowed or not
+ // (0 -> delay is not allowed, 1 -> delay is allowed)
+ // next k elements in dependencies -> transaction indexes on which transaction i is dependent on
+ dependencies []int
+ coinbase common.Address
+ blockContext vm.BlockContext
+}
+
+func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (err error) {
+ task.statedb = task.cleanStateDB.Copy()
+ task.statedb.Prepare(task.tx.Hash(), task.index)
+ task.statedb.SetMVHashmap(mvh)
+ task.statedb.SetIncarnation(incarnation)
+
+ evm := vm.NewEVM(task.blockContext, vm.TxContext{}, task.statedb, task.config, task.evmConfig)
+
+ // Create a new context to be used in the EVM environment.
+ txContext := NewEVMTxContext(task.msg)
+ evm.Reset(txContext, task.statedb)
+
+ defer func() {
+ if r := recover(); r != nil {
+ // In some pre-matured executions, EVM will panic. Recover from panic and retry the execution.
+ log.Debug("Recovered from EVM failure.", "Error:", r)
+
+ err = blockstm.ErrExecAbortError{Dependency: task.statedb.DepTxIndex()}
+
+ return
+ }
+ }()
+
+ // Apply the transaction to the current state (included in the env).
+ if *task.shouldDelayFeeCal {
+ task.result, err = ApplyMessageNoFeeBurnOrTip(evm, task.msg, new(GasPool).AddGas(task.gasLimit), nil)
+
+ if task.result == nil || err != nil {
+ return blockstm.ErrExecAbortError{Dependency: task.statedb.DepTxIndex(), OriginError: err}
+ }
+
+ reads := task.statedb.MVReadMap()
+
+ if _, ok := reads[blockstm.NewSubpathKey(task.blockContext.Coinbase, state.BalancePath)]; ok {
+ log.Info("Coinbase is in MVReadMap", "address", task.blockContext.Coinbase)
+
+ task.shouldRerunWithoutFeeDelay = true
+ }
+
+ if _, ok := reads[blockstm.NewSubpathKey(task.result.BurntContractAddress, state.BalancePath)]; ok {
+ log.Info("BurntContractAddress is in MVReadMap", "address", task.result.BurntContractAddress)
+
+ task.shouldRerunWithoutFeeDelay = true
+ }
+ } else {
+ task.result, err = ApplyMessage(evm, task.msg, new(GasPool).AddGas(task.gasLimit), nil)
+ }
+
+ if task.statedb.HadInvalidRead() || err != nil {
+ err = blockstm.ErrExecAbortError{Dependency: task.statedb.DepTxIndex(), OriginError: err}
+ return
+ }
+
+ task.statedb.Finalise(task.config.IsEIP158(task.blockNumber))
+
+ return
+}
+
+func (task *ExecutionTask) MVReadList() []blockstm.ReadDescriptor {
+ return task.statedb.MVReadList()
+}
+
+func (task *ExecutionTask) MVWriteList() []blockstm.WriteDescriptor {
+ return task.statedb.MVWriteList()
+}
+
+func (task *ExecutionTask) MVFullWriteList() []blockstm.WriteDescriptor {
+ return task.statedb.MVFullWriteList()
+}
+
+func (task *ExecutionTask) Sender() common.Address {
+ return task.sender
+}
+
+func (task *ExecutionTask) Hash() common.Hash {
+ return task.tx.Hash()
+}
+
+func (task *ExecutionTask) Dependencies() []int {
+ return task.dependencies
+}
+
+func (task *ExecutionTask) Settle() {
+ task.finalStateDB.Prepare(task.tx.Hash(), task.index)
+
+ coinbaseBalance := task.finalStateDB.GetBalance(task.coinbase)
+
+ task.finalStateDB.ApplyMVWriteSet(task.statedb.MVFullWriteList())
+
+ for _, l := range task.statedb.GetLogs(task.tx.Hash(), task.blockHash) {
+ task.finalStateDB.AddLog(l)
+ }
+
+ if *task.shouldDelayFeeCal {
+ if task.config.IsLondon(task.blockNumber) {
+ task.finalStateDB.AddBalance(task.result.BurntContractAddress, task.result.FeeBurnt)
+ }
+
+ task.finalStateDB.AddBalance(task.coinbase, task.result.FeeTipped)
+ output1 := new(big.Int).SetBytes(task.result.SenderInitBalance.Bytes())
+ output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes())
+
+ // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
+ // add transfer log
+ AddFeeTransferLog(
+ task.finalStateDB,
+
+ task.msg.From(),
+ task.coinbase,
+
+ task.result.FeeTipped,
+ task.result.SenderInitBalance,
+ coinbaseBalance,
+ output1.Sub(output1, task.result.FeeTipped),
+ output2.Add(output2, task.result.FeeTipped),
+ )
+ }
+
+ for k, v := range task.statedb.Preimages() {
+ task.finalStateDB.AddPreimage(k, v)
+ }
+
+ // Update the state with pending changes.
+ var root []byte
+
+ if task.config.IsByzantium(task.blockNumber) {
+ task.finalStateDB.Finalise(true)
+ } else {
+ root = task.finalStateDB.IntermediateRoot(task.config.IsEIP158(task.blockNumber)).Bytes()
+ }
+
+ *task.totalUsedGas += task.result.UsedGas
+
+ // Create a new receipt for the transaction, storing the intermediate root and gas used
+ // by the tx.
+ receipt := &types.Receipt{Type: task.tx.Type(), PostState: root, CumulativeGasUsed: *task.totalUsedGas}
+ if task.result.Failed() {
+ receipt.Status = types.ReceiptStatusFailed
+ } else {
+ receipt.Status = types.ReceiptStatusSuccessful
+ }
+
+ receipt.TxHash = task.tx.Hash()
+ receipt.GasUsed = task.result.UsedGas
+
+ // If the transaction created a contract, store the creation address in the receipt.
+ if task.msg.To() == nil {
+ receipt.ContractAddress = crypto.CreateAddress(task.msg.From(), task.tx.Nonce())
+ }
+
+ // Set the receipt logs and create the bloom filter.
+ receipt.Logs = task.finalStateDB.GetLogs(task.tx.Hash(), task.blockHash)
+ receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
+ receipt.BlockHash = task.blockHash
+ receipt.BlockNumber = task.blockNumber
+ receipt.TransactionIndex = uint(task.finalStateDB.TxIndex())
+
+ *task.receipts = append(*task.receipts, receipt)
+ *task.allLogs = append(*task.allLogs, receipt.Logs...)
+}
+
+var parallelizabilityTimer = metrics.NewRegisteredTimer("block/parallelizability", nil)
+
+// Process processes the state changes according to the Ethereum rules by running
+// the transaction messages using the statedb and applying any rewards to both
+// the processor (coinbase) and any included uncles.
+//
+// Process returns the receipts and logs accumulated during the process and
+// returns the amount of gas that was used in the process. If any of the
+// transactions failed to execute due to insufficient gas it will return an error.
+// nolint:gocognit
+func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, interruptCtx context.Context) (types.Receipts, []*types.Log, uint64, error) {
+ blockstm.SetProcs(cfg.ParallelSpeculativeProcesses)
+
+ var (
+ receipts types.Receipts
+ header = block.Header()
+ blockHash = block.Hash()
+ blockNumber = block.Number()
+ allLogs []*types.Log
+ usedGas = new(uint64)
+ metadata bool
+ )
+
+ // Mutate the block and state according to any hard-fork specs
+ if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
+ misc.ApplyDAOHardFork(statedb)
+ }
+
+ tasks := make([]blockstm.ExecTask, 0, len(block.Transactions()))
+
+ shouldDelayFeeCal := true
+
+ coinbase, _ := p.bc.Engine().Author(header)
+
+ deps := GetDeps(block.Header().TxDependency)
+
+ if block.Header().TxDependency != nil {
+ metadata = true
+ }
+
+ blockContext := NewEVMBlockContext(header, p.bc, nil)
+
+ // Iterate over and process the individual transactions
+ for i, tx := range block.Transactions() {
+ msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
+ if err != nil {
+ log.Error("error creating message", "err", err)
+ return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
+ }
+
+ cleansdb := statedb.Copy()
+
+ if msg.From() == coinbase {
+ shouldDelayFeeCal = false
+ }
+
+ if len(header.TxDependency) != len(block.Transactions()) {
+ task := &ExecutionTask{
+ msg: msg,
+ config: p.config,
+ gasLimit: block.GasLimit(),
+ blockNumber: blockNumber,
+ blockHash: blockHash,
+ tx: tx,
+ index: i,
+ cleanStateDB: cleansdb,
+ finalStateDB: statedb,
+ blockChain: p.bc,
+ header: header,
+ evmConfig: cfg,
+ shouldDelayFeeCal: &shouldDelayFeeCal,
+ sender: msg.From(),
+ totalUsedGas: usedGas,
+ receipts: &receipts,
+ allLogs: &allLogs,
+ dependencies: deps[i],
+ coinbase: coinbase,
+ blockContext: blockContext,
+ }
+
+ tasks = append(tasks, task)
+ } else {
+ task := &ExecutionTask{
+ msg: msg,
+ config: p.config,
+ gasLimit: block.GasLimit(),
+ blockNumber: blockNumber,
+ blockHash: blockHash,
+ tx: tx,
+ index: i,
+ cleanStateDB: cleansdb,
+ finalStateDB: statedb,
+ blockChain: p.bc,
+ header: header,
+ evmConfig: cfg,
+ shouldDelayFeeCal: &shouldDelayFeeCal,
+ sender: msg.From(),
+ totalUsedGas: usedGas,
+ receipts: &receipts,
+ allLogs: &allLogs,
+ dependencies: nil,
+ coinbase: coinbase,
+ blockContext: blockContext,
+ }
+
+ tasks = append(tasks, task)
+ }
+ }
+
+ backupStateDB := statedb.Copy()
+
+ profile := false
+ result, err := blockstm.ExecuteParallel(tasks, profile, metadata, interruptCtx)
+
+ if err == nil && profile && result.Deps != nil {
+ _, weight := result.Deps.LongestPath(*result.Stats)
+
+ serialWeight := uint64(0)
+
+ for i := 0; i < len(result.Deps.GetVertices()); i++ {
+ serialWeight += (*result.Stats)[i].End - (*result.Stats)[i].Start
+ }
+
+ parallelizabilityTimer.Update(time.Duration(serialWeight * 100 / weight))
+ }
+
+ for _, task := range tasks {
+ task := task.(*ExecutionTask)
+ if task.shouldRerunWithoutFeeDelay {
+ shouldDelayFeeCal = false
+
+ statedb.StopPrefetcher()
+ *statedb = *backupStateDB
+
+ allLogs = []*types.Log{}
+ receipts = types.Receipts{}
+ usedGas = new(uint64)
+
+ for _, t := range tasks {
+ t := t.(*ExecutionTask)
+ t.finalStateDB = backupStateDB
+ t.allLogs = &allLogs
+ t.receipts = &receipts
+ t.totalUsedGas = usedGas
+ }
+
+ _, err = blockstm.ExecuteParallel(tasks, false, metadata, interruptCtx)
+
+ break
+ }
+ }
+
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
+ p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())
+
+ return receipts, allLogs, *usedGas, nil
+}
+
+func GetDeps(txDependency [][]uint64) map[int][]int {
+ deps := make(map[int][]int)
+
+ for i := 0; i <= len(txDependency)-1; i++ {
+ deps[i] = []int{}
+
+ for j := 0; j <= len(txDependency[i])-1; j++ {
+ deps[i] = append(deps[i], int(txDependency[i][j]))
+ }
+ }
+
+ return deps
+}
diff --git a/core/state/journal.go b/core/state/journal.go
index 1722fb4c02..0d2155398f 100644
--- a/core/state/journal.go
+++ b/core/state/journal.go
@@ -20,6 +20,7 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/blockstm"
)
// journalEntry is a modification entry in the state change journal that can be
@@ -148,6 +149,7 @@ type (
func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, *ch.account)
delete(s.stateObjectsDirty, *ch.account)
+ RevertWrite(s, blockstm.NewAddressKey(*ch.account))
}
func (ch createObjectChange) dirtied() *common.Address {
@@ -156,6 +158,7 @@ func (ch createObjectChange) dirtied() *common.Address {
func (ch resetObjectChange) revert(s *StateDB) {
s.setStateObject(ch.prev)
+ RevertWrite(s, blockstm.NewAddressKey(ch.prev.address))
if !ch.prevdestruct {
delete(s.stateObjectsDestruct, ch.prev.address)
}
@@ -170,6 +173,7 @@ func (ch suicideChange) revert(s *StateDB) {
if obj != nil {
obj.suicided = ch.prev
obj.setBalance(ch.prevbalance)
+ RevertWrite(s, blockstm.NewSubpathKey(*ch.account, SuicidePath))
}
}
@@ -204,6 +208,7 @@ func (ch nonceChange) dirtied() *common.Address {
func (ch codeChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
+ RevertWrite(s, blockstm.NewSubpathKey(*ch.account, CodePath))
}
func (ch codeChange) dirtied() *common.Address {
@@ -212,6 +217,7 @@ func (ch codeChange) dirtied() *common.Address {
func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
+ RevertWrite(s, blockstm.NewStateKey(*ch.account, ch.key))
}
func (ch storageChange) dirtied() *common.Address {
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 9ba8604bfb..b68480989e 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -25,6 +25,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/blockstm"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
@@ -78,6 +79,14 @@ type StateDB struct {
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block
+ // Block-stm related fields
+ mvHashmap *blockstm.MVHashMap
+ incarnation int
+ readMap map[blockstm.Key]blockstm.ReadDescriptor
+ writeMap map[blockstm.Key]blockstm.WriteDescriptor
+ revertedKeys map[blockstm.Key]struct{}
+ dep int
+
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
@@ -144,6 +153,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]struct{}),
+ revertedKeys: make(map[blockstm.Key]struct{}),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
@@ -160,6 +170,281 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
return sdb, nil
}
+func NewWithMVHashmap(root common.Hash, db Database, snaps *snapshot.Tree, mvhm *blockstm.MVHashMap) (*StateDB, error) {
+ if sdb, err := New(root, db, snaps); err != nil {
+ return nil, err
+ } else {
+ sdb.mvHashmap = mvhm
+ sdb.dep = -1
+ return sdb, nil
+ }
+}
+
+func (s *StateDB) SetMVHashmap(mvhm *blockstm.MVHashMap) {
+ s.mvHashmap = mvhm
+ s.dep = -1
+}
+
+func (s *StateDB) GetMVHashmap() *blockstm.MVHashMap {
+ return s.mvHashmap
+}
+
+func (s *StateDB) MVWriteList() []blockstm.WriteDescriptor {
+ writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap))
+
+ for _, v := range s.writeMap {
+ if _, ok := s.revertedKeys[v.Path]; !ok {
+ writes = append(writes, v)
+ }
+ }
+
+ return writes
+}
+
+func (s *StateDB) MVFullWriteList() []blockstm.WriteDescriptor {
+ writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap))
+
+ for _, v := range s.writeMap {
+ writes = append(writes, v)
+ }
+
+ return writes
+}
+
+func (s *StateDB) MVReadMap() map[blockstm.Key]blockstm.ReadDescriptor {
+ return s.readMap
+}
+
+func (s *StateDB) MVReadList() []blockstm.ReadDescriptor {
+ reads := make([]blockstm.ReadDescriptor, 0, len(s.readMap))
+
+ for _, v := range s.MVReadMap() {
+ reads = append(reads, v)
+ }
+
+ return reads
+}
+
+func (s *StateDB) ensureReadMap() {
+ if s.readMap == nil {
+ s.readMap = make(map[blockstm.Key]blockstm.ReadDescriptor)
+ }
+}
+
+func (s *StateDB) ensureWriteMap() {
+ if s.writeMap == nil {
+ s.writeMap = make(map[blockstm.Key]blockstm.WriteDescriptor)
+ }
+}
+
+func (s *StateDB) ClearReadMap() {
+ s.readMap = make(map[blockstm.Key]blockstm.ReadDescriptor)
+}
+
+func (s *StateDB) ClearWriteMap() {
+ s.writeMap = make(map[blockstm.Key]blockstm.WriteDescriptor)
+}
+
+func (s *StateDB) HadInvalidRead() bool {
+ return s.dep >= 0
+}
+
+func (s *StateDB) DepTxIndex() int {
+ return s.dep
+}
+
+func (s *StateDB) SetIncarnation(inc int) {
+ s.incarnation = inc
+}
+
+type StorageVal[T any] struct {
+ Value *T
+}
+
+func MVRead[T any](s *StateDB, k blockstm.Key, defaultV T, readStorage func(s *StateDB) T) (v T) {
+ if s.mvHashmap == nil {
+ return readStorage(s)
+ }
+
+ s.ensureReadMap()
+
+ if s.writeMap != nil {
+ if _, ok := s.writeMap[k]; ok {
+ return readStorage(s)
+ }
+ }
+
+ if !k.IsAddress() {
+ // If we are reading subpath from a deleted account, return default value instead of reading from MVHashmap
+ addr := k.GetAddress()
+ if s.getStateObject(addr) == nil {
+ return defaultV
+ }
+ }
+
+ res := s.mvHashmap.Read(k, s.txIndex)
+
+ var rd blockstm.ReadDescriptor
+
+ rd.V = blockstm.Version{
+ TxnIndex: res.DepIdx(),
+ Incarnation: res.Incarnation(),
+ }
+
+ rd.Path = k
+
+ switch res.Status() {
+ case blockstm.MVReadResultDone:
+ {
+ v = readStorage(res.Value().(*StateDB))
+ rd.Kind = blockstm.ReadKindMap
+ }
+ case blockstm.MVReadResultDependency:
+ {
+ s.dep = res.DepIdx()
+
+ panic("Found dependency")
+ }
+ case blockstm.MVReadResultNone:
+ {
+ v = readStorage(s)
+ rd.Kind = blockstm.ReadKindStorage
+ }
+ default:
+ return defaultV
+ }
+
+ // TODO: I assume we don't want to overwrite an existing read because this could - for example - change a storage
+ // read to map if the same value is read multiple times.
+ if _, ok := s.readMap[k]; !ok {
+ s.readMap[k] = rd
+ }
+
+ return
+}
+
+func MVWrite(s *StateDB, k blockstm.Key) {
+ if s.mvHashmap != nil {
+ s.ensureWriteMap()
+ s.writeMap[k] = blockstm.WriteDescriptor{
+ Path: k,
+ V: s.Version(),
+ Val: s,
+ }
+ }
+}
+
+func RevertWrite(s *StateDB, k blockstm.Key) {
+ s.revertedKeys[k] = struct{}{}
+}
+
+func MVWritten(s *StateDB, k blockstm.Key) bool {
+ if s.mvHashmap == nil || s.writeMap == nil {
+ return false
+ }
+
+ _, ok := s.writeMap[k]
+
+ return ok
+}
+
+// FlushMVWriteSet applies entries in the write set to MVHashMap. Note that this function does not clear the write set.
+func (s *StateDB) FlushMVWriteSet() {
+ if s.mvHashmap != nil && s.writeMap != nil {
+ s.mvHashmap.FlushMVWriteSet(s.MVFullWriteList())
+ }
+}
+
+// ApplyMVWriteSet applies entries in a given write set to StateDB. Note that this function does not change MVHashMap nor write set
+// of the current StateDB.
+func (s *StateDB) ApplyMVWriteSet(writes []blockstm.WriteDescriptor) {
+ for i := range writes {
+ path := writes[i].Path
+ sr := writes[i].Val.(*StateDB)
+
+ if path.IsState() {
+ addr := path.GetAddress()
+ stateKey := path.GetStateKey()
+ state := sr.GetState(addr, stateKey)
+ s.SetState(addr, stateKey, state)
+ } else if path.IsAddress() {
+ continue
+ } else {
+ addr := path.GetAddress()
+ switch path.GetSubpath() {
+ case BalancePath:
+ s.SetBalance(addr, sr.GetBalance(addr))
+ case NoncePath:
+ s.SetNonce(addr, sr.GetNonce(addr))
+ case CodePath:
+ s.SetCode(addr, sr.GetCode(addr))
+ case SuicidePath:
+ stateObject := sr.getDeletedStateObject(addr)
+ if stateObject != nil && stateObject.deleted {
+ s.Suicide(addr)
+ }
+ default:
+ panic(fmt.Errorf("unknown key type: %d", path.GetSubpath()))
+ }
+ }
+ }
+}
+
+type DumpStruct struct {
+ TxIdx int
+ TxInc int
+ VerIdx int
+ VerInc int
+ Path []byte
+ Op string
+}
+
+// GetReadMapDump gets readMap Dump of format: "TxIdx, Inc, Path, Read"
+func (s *StateDB) GetReadMapDump() []DumpStruct {
+ readList := s.MVReadList()
+ res := make([]DumpStruct, 0, len(readList))
+
+ for _, val := range readList {
+ temp := &DumpStruct{
+ TxIdx: s.txIndex,
+ TxInc: s.incarnation,
+ VerIdx: val.V.TxnIndex,
+ VerInc: val.V.Incarnation,
+ Path: val.Path[:],
+ Op: "Read\n",
+ }
+ res = append(res, *temp)
+ }
+
+ return res
+}
+
+// GetWriteMapDump gets writeMap Dump of format: "TxIdx, Inc, Path, Write"
+func (s *StateDB) GetWriteMapDump() []DumpStruct {
+ writeList := s.MVReadList()
+ res := make([]DumpStruct, 0, len(writeList))
+
+ for _, val := range writeList {
+ temp := &DumpStruct{
+ TxIdx: s.txIndex,
+ TxInc: s.incarnation,
+ VerIdx: val.V.TxnIndex,
+ VerInc: val.V.Incarnation,
+ Path: val.Path[:],
+ Op: "Write\n",
+ }
+ res = append(res, *temp)
+ }
+
+ return res
+}
+
+// AddEmptyMVHashMap adds empty MVHashMap to StateDB
+func (s *StateDB) AddEmptyMVHashMap() {
+ mvh := blockstm.MakeMVHashMap()
+ s.mvHashmap = mvh
+}
+
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot.
@@ -267,22 +552,40 @@ func (s *StateDB) Empty(addr common.Address) bool {
return so == nil || so.empty()
}
+// Create a unique path for special fields (e.g. balance, code) in a state object.
+// func subPath(prefix []byte, s uint8) [blockstm.KeyLength]byte {
+// path := append(prefix, common.Hash{}.Bytes()...) // append a full empty hash to avoid collision with storage state
+// path = append(path, s) // append the special field identifier
+
+// return path
+// }
+
+const BalancePath = 1
+const NoncePath = 2
+const CodePath = 3
+const SuicidePath = 4
+
// GetBalance retrieves the balance from the given address or 0 if object not found
func (s *StateDB) GetBalance(addr common.Address) *big.Int {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.Balance()
- }
- return common.Big0
+ return MVRead(s, blockstm.NewSubpathKey(addr, BalancePath), common.Big0, func(s *StateDB) *big.Int {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.Balance()
+ }
+
+ return common.Big0
+ })
}
func (s *StateDB) GetNonce(addr common.Address) uint64 {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.Nonce()
- }
+ return MVRead(s, blockstm.NewSubpathKey(addr, NoncePath), 0, func(s *StateDB) uint64 {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.Nonce()
+ }
- return 0
+ return 0
+ })
}
// TxIndex returns the current transaction index set by Prepare.
@@ -290,37 +593,52 @@ func (s *StateDB) TxIndex() int {
return s.txIndex
}
-func (s *StateDB) GetCode(addr common.Address) []byte {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.Code(s.db)
+func (s *StateDB) Version() blockstm.Version {
+ return blockstm.Version{
+ TxnIndex: s.txIndex,
+ Incarnation: s.incarnation,
}
- return nil
+}
+
+func (s *StateDB) GetCode(addr common.Address) []byte {
+ return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), nil, func(s *StateDB) []byte {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.Code(s.db)
+ }
+ return nil
+ })
}
func (s *StateDB) GetCodeSize(addr common.Address) int {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.CodeSize(s.db)
- }
- return 0
+ return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), 0, func(s *StateDB) int {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.CodeSize(s.db)
+ }
+ return 0
+ })
}
func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
- stateObject := s.getStateObject(addr)
- if stateObject == nil {
- return common.Hash{}
- }
- return common.BytesToHash(stateObject.CodeHash())
+ return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), common.Hash{}, func(s *StateDB) common.Hash {
+ stateObject := s.getStateObject(addr)
+ if stateObject == nil {
+ return common.Hash{}
+ }
+ return common.BytesToHash(stateObject.CodeHash())
+ })
}
// GetState retrieves a value from the given account's storage trie.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.GetState(s.db, hash)
- }
- return common.Hash{}
+ return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.GetState(s.db, hash)
+ }
+ return common.Hash{}
+ })
}
// GetProof returns the Merkle proof for a given account.
@@ -354,11 +672,13 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
// GetCommittedState retrieves a value from the given account's committed storage trie.
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.GetCommittedState(s.db, hash)
- }
- return common.Hash{}
+ return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.GetCommittedState(s.db, hash)
+ }
+ return common.Hash{}
+ })
}
// Database retrieves the low level database supporting the lower level trie ops.
@@ -382,11 +702,13 @@ func (s *StateDB) StorageTrie(addr common.Address) (Trie, error) {
}
func (s *StateDB) HasSuicided(addr common.Address) bool {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.suicided
- }
- return false
+ return MVRead(s, blockstm.NewSubpathKey(addr, SuicidePath), false, func(s *StateDB) bool {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.suicided
+ }
+ return false
+ })
}
/*
@@ -396,44 +718,68 @@ func (s *StateDB) HasSuicided(addr common.Address) bool {
// AddBalance adds amount to the account associated with addr.
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
stateObject := s.GetOrNewStateObject(addr)
+
+ if s.mvHashmap != nil {
+ // ensure a read balance operation is recorded in mvHashmap
+ s.GetBalance(addr)
+ }
+
if stateObject != nil {
+ stateObject = s.mvRecordWritten(stateObject)
stateObject.AddBalance(amount)
+ MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
}
}
// SubBalance subtracts amount from the account associated with addr.
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
stateObject := s.GetOrNewStateObject(addr)
+
+ if s.mvHashmap != nil {
+ // ensure a read balance operation is recorded in mvHashmap
+ s.GetBalance(addr)
+ }
+
if stateObject != nil {
+ stateObject = s.mvRecordWritten(stateObject)
stateObject.SubBalance(amount)
+ MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
}
}
func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
+ stateObject = s.mvRecordWritten(stateObject)
stateObject.SetBalance(amount)
+ MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
}
}
func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
+ stateObject = s.mvRecordWritten(stateObject)
stateObject.SetNonce(nonce)
+ MVWrite(s, blockstm.NewSubpathKey(addr, NoncePath))
}
}
func (s *StateDB) SetCode(addr common.Address, code []byte) {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
+ stateObject = s.mvRecordWritten(stateObject)
stateObject.SetCode(crypto.Keccak256Hash(code), code)
+ MVWrite(s, blockstm.NewSubpathKey(addr, CodePath))
}
}
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
+ stateObject = s.mvRecordWritten(stateObject)
stateObject.SetState(s.db, key, value)
+ MVWrite(s, blockstm.NewStateKey(addr, key))
}
}
@@ -462,6 +808,8 @@ func (s *StateDB) Suicide(addr common.Address) bool {
if stateObject == nil {
return false
}
+
+ stateObject = s.mvRecordWritten(stateObject)
s.journal.append(suicideChange{
account: &addr,
prev: stateObject.suicided,
@@ -470,6 +818,9 @@ func (s *StateDB) Suicide(addr common.Address) bool {
stateObject.markSuicided()
stateObject.data.Balance = new(big.Int)
+ MVWrite(s, blockstm.NewSubpathKey(addr, SuicidePath))
+ MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
+
return true
}
@@ -553,56 +904,58 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
// flag set. This is needed by the state journal to revert to the correct s-
// destructed object instead of wiping all knowledge about the state object.
func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
- // Prefer live objects if any is available
- if obj := s.stateObjects[addr]; obj != nil {
- return obj
- }
- // If no live objects are available, attempt to use snapshots
- var data *types.StateAccount
- if s.snap != nil {
- start := time.Now()
- acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes()))
- if metrics.EnabledExpensive {
- s.SnapshotAccountReads += time.Since(start)
+ return MVRead(s, blockstm.NewAddressKey(addr), nil, func(s *StateDB) *stateObject {
+ // Prefer live objects if any is available
+ if obj := s.stateObjects[addr]; obj != nil {
+ return obj
}
- if err == nil {
- if acc == nil {
- return nil
+ // If no live objects are available, attempt to use snapshots
+ var data *types.StateAccount
+ if s.snap != nil { // nolint
+ start := time.Now()
+ acc, err := s.snap.Account(crypto.HashData(crypto.NewKeccakState(), addr.Bytes()))
+ if metrics.EnabledExpensive {
+ s.SnapshotAccountReads += time.Since(start)
}
- data = &types.StateAccount{
- Nonce: acc.Nonce,
- Balance: acc.Balance,
- CodeHash: acc.CodeHash,
- Root: common.BytesToHash(acc.Root),
- }
- if len(data.CodeHash) == 0 {
+ if err == nil {
+ if acc == nil {
+ return nil
+ }
+ data = &types.StateAccount{
+ Nonce: acc.Nonce,
+ Balance: acc.Balance,
+ CodeHash: acc.CodeHash,
+ Root: common.BytesToHash(acc.Root),
+ }
+ if len(data.CodeHash) == 0 {
data.CodeHash = types.EmptyCodeHash.Bytes()
- }
- if data.Root == (common.Hash{}) {
+ }
+ if data.Root == (common.Hash{}) {
data.Root = types.EmptyRootHash
+ }
}
}
- }
- // If snapshot unavailable or reading from it failed, load from the database
- if data == nil {
- start := time.Now()
+ // If snapshot unavailable or reading from it failed, load from the database
+ if data == nil {
+ start := time.Now()
var err error
data, err = s.trie.GetAccount(addr)
- if metrics.EnabledExpensive {
- s.AccountReads += time.Since(start)
- }
- if err != nil {
+ if metrics.EnabledExpensive {
+ s.AccountReads += time.Since(start)
+ }
+ if err != nil {
s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err))
- return nil
- }
+ return nil
+ }
if data == nil {
- return nil
+ return nil
+ }
}
- }
- // Insert into the live set
- obj := newObject(s, addr, *data)
- s.setStateObject(obj)
- return obj
+ // Insert into the live set
+ obj := newObject(s, addr, *data)
+ s.setStateObject(obj)
+ return obj
+ })
}
func (s *StateDB) setStateObject(object *stateObject) {
@@ -618,6 +971,28 @@ func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
return stateObject
}
+// mvRecordWritten checks whether a state object is already present in the current MV writeMap.
+// If yes, it returns the object directly.
+// If not, it clones the object and inserts it into the writeMap before returning it.
+func (s *StateDB) mvRecordWritten(object *stateObject) *stateObject {
+ if s.mvHashmap == nil {
+ return object
+ }
+
+ addrKey := blockstm.NewAddressKey(object.Address())
+
+ if MVWritten(s, addrKey) {
+ return object
+ }
+
+ // Deepcopy is needed to ensure that objects are not written by multiple transactions at the same time, because
+ // the input state object can come from a different transaction.
+ s.setStateObject(object.deepCopy(s))
+ MVWrite(s, addrKey)
+
+ return s.stateObjects[object.Address()]
+}
+
// createObject creates a new state object. If there is an existing account with
// the given address, it is overwritten and returned as the second return value.
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
@@ -637,6 +1012,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
}
s.setStateObject(newobj)
+
+ MVWrite(s, blockstm.NewAddressKey(addr))
if prev != nil && !prev.deleted {
return newobj, prev
}
@@ -657,22 +1034,23 @@ func (s *StateDB) CreateAccount(addr common.Address) {
newObj, prev := s.createObject(addr)
if prev != nil {
newObj.setBalance(prev.data.Balance)
+ MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
}
}
-func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
- so := db.getStateObject(addr)
+func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
+ so := s.getStateObject(addr)
if so == nil {
return nil
}
- tr, err := so.getTrie(db.db)
+ tr, err := so.getTrie(s.db)
if err != nil {
return err
}
it := trie.NewIterator(tr.NodeIterator(nil))
for it.Next() {
- key := common.BytesToHash(db.trie.GetKey(it.Key))
+ key := common.BytesToHash(s.trie.GetKey(it.Key))
if value, dirty := so.dirtyStorage[key]; dirty {
if !cb(key, value) {
return nil
@@ -705,6 +1083,7 @@ func (s *StateDB) Copy() *StateDB {
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)),
+ revertedKeys: make(map[blockstm.Key]struct{}),
refund: s.refund,
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize,
@@ -796,6 +1175,10 @@ func (s *StateDB) Copy() *StateDB {
state.snapStorage[k] = temp
}
}
+
+ if s.mvHashmap != nil {
+ state.mvHashmap = s.mvHashmap
+ }
return state
}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 8aa59e3ee5..7f4c2c848e 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -29,7 +29,10 @@ import (
"testing"
"testing/quick"
+ "github.com/stretchr/testify/assert"
+
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/blockstm"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
)
@@ -498,6 +501,457 @@ func TestTouchDelete(t *testing.T) {
}
}
+func TestMVHashMapReadWriteDelete(t *testing.T) {
+ t.Parallel()
+
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ mvhm := blockstm.MakeMVHashMap()
+ s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
+
+ states := []*StateDB{s}
+
+ // Create copies of the original state for each transition
+ for i := 1; i <= 4; i++ {
+ sCopy := s.Copy()
+ sCopy.txIndex = i
+ states = append(states, sCopy)
+ }
+
+ addr := common.HexToAddress("0x01")
+ key := common.HexToHash("0x01")
+ val := common.HexToHash("0x01")
+ balance := new(big.Int).SetUint64(uint64(100))
+
+ // Tx0 read
+ v := states[0].GetState(addr, key)
+
+ assert.Equal(t, common.Hash{}, v)
+
+ // Tx1 write
+ states[1].GetOrNewStateObject(addr)
+ states[1].SetState(addr, key, val)
+ states[1].SetBalance(addr, balance)
+ states[1].FlushMVWriteSet()
+
+ // Tx1 read
+ v = states[1].GetState(addr, key)
+ b := states[1].GetBalance(addr)
+
+ assert.Equal(t, val, v)
+ assert.Equal(t, balance, b)
+
+ // Tx2 read
+ v = states[2].GetState(addr, key)
+ b = states[2].GetBalance(addr)
+
+ assert.Equal(t, val, v)
+ assert.Equal(t, balance, b)
+
+ // Tx3 delete
+ states[3].Suicide(addr)
+
+ // Within Tx 3, the state should not change before finalize
+ v = states[3].GetState(addr, key)
+ assert.Equal(t, val, v)
+
+ // After finalizing Tx 3, the state will change
+ states[3].Finalise(false)
+ v = states[3].GetState(addr, key)
+ assert.Equal(t, common.Hash{}, v)
+ states[3].FlushMVWriteSet()
+
+ // Tx4 read
+ v = states[4].GetState(addr, key)
+ b = states[4].GetBalance(addr)
+
+ assert.Equal(t, common.Hash{}, v)
+ assert.Equal(t, common.Big0, b)
+}
+
+func TestMVHashMapRevert(t *testing.T) {
+ t.Parallel()
+
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ mvhm := blockstm.MakeMVHashMap()
+ s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
+
+ states := []*StateDB{s}
+
+ // Create copies of the original state for each transition
+ for i := 1; i <= 4; i++ {
+ sCopy := s.Copy()
+ sCopy.txIndex = i
+ states = append(states, sCopy)
+ }
+
+ addr := common.HexToAddress("0x01")
+ key := common.HexToHash("0x01")
+ val := common.HexToHash("0x01")
+ balance := new(big.Int).SetUint64(uint64(100))
+
+ // Tx0 write
+ states[0].GetOrNewStateObject(addr)
+ states[0].SetState(addr, key, val)
+ states[0].SetBalance(addr, balance)
+ states[0].FlushMVWriteSet()
+
+ // Tx1 perform some ops and then revert
+ snapshot := states[1].Snapshot()
+ states[1].AddBalance(addr, new(big.Int).SetUint64(uint64(100)))
+ states[1].SetState(addr, key, common.HexToHash("0x02"))
+ v := states[1].GetState(addr, key)
+ b := states[1].GetBalance(addr)
+ assert.Equal(t, new(big.Int).SetUint64(uint64(200)), b)
+ assert.Equal(t, common.HexToHash("0x02"), v)
+
+ states[1].Suicide(addr)
+
+ states[1].RevertToSnapshot(snapshot)
+
+ v = states[1].GetState(addr, key)
+ b = states[1].GetBalance(addr)
+
+ assert.Equal(t, val, v)
+ assert.Equal(t, balance, b)
+ states[1].Finalise(false)
+ states[1].FlushMVWriteSet()
+
+ // Tx2 check the state and balance
+ v = states[2].GetState(addr, key)
+ b = states[2].GetBalance(addr)
+
+ assert.Equal(t, val, v)
+ assert.Equal(t, balance, b)
+}
+
+func TestMVHashMapMarkEstimate(t *testing.T) {
+ t.Parallel()
+
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ mvhm := blockstm.MakeMVHashMap()
+ s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
+
+ states := []*StateDB{s}
+
+ // Create copies of the original state for each transition
+ for i := 1; i <= 4; i++ {
+ sCopy := s.Copy()
+ sCopy.txIndex = i
+ states = append(states, sCopy)
+ }
+
+ addr := common.HexToAddress("0x01")
+ key := common.HexToHash("0x01")
+ val := common.HexToHash("0x01")
+ balance := new(big.Int).SetUint64(uint64(100))
+
+ // Tx0 read
+ v := states[0].GetState(addr, key)
+ assert.Equal(t, common.Hash{}, v)
+
+ // Tx0 write
+ states[0].SetState(addr, key, val)
+ v = states[0].GetState(addr, key)
+ assert.Equal(t, val, v)
+ states[0].FlushMVWriteSet()
+
+ // Tx1 write
+ states[1].GetOrNewStateObject(addr)
+ states[1].SetState(addr, key, val)
+ states[1].SetBalance(addr, balance)
+ states[1].FlushMVWriteSet()
+
+ // Tx2 read
+ v = states[2].GetState(addr, key)
+ b := states[2].GetBalance(addr)
+
+ assert.Equal(t, val, v)
+ assert.Equal(t, balance, b)
+
+ // Tx1 mark estimate
+ for _, v := range states[1].MVWriteList() {
+ mvhm.MarkEstimate(v.Path, 1)
+ }
+
+ defer func() {
+ if r := recover(); r == nil {
+ t.Errorf("The code did not panic")
+ } else {
+ t.Log("Recovered in f", r)
+ }
+ }()
+
+ // Tx2 read again should get default (empty) vals because its dependency Tx1 is marked as estimate
+ states[2].GetState(addr, key)
+ states[2].GetBalance(addr)
+
+ // Tx1 read again should get Tx0 vals
+ v = states[1].GetState(addr, key)
+ assert.Equal(t, val, v)
+}
+
+func TestMVHashMapOverwrite(t *testing.T) {
+ t.Parallel()
+
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ mvhm := blockstm.MakeMVHashMap()
+ s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
+
+ states := []*StateDB{s}
+
+ // Create copies of the original state for each transition
+ for i := 1; i <= 4; i++ {
+ sCopy := s.Copy()
+ sCopy.txIndex = i
+ states = append(states, sCopy)
+ }
+
+ addr := common.HexToAddress("0x01")
+ key := common.HexToHash("0x01")
+ val1 := common.HexToHash("0x01")
+ balance1 := new(big.Int).SetUint64(uint64(100))
+ val2 := common.HexToHash("0x02")
+ balance2 := new(big.Int).SetUint64(uint64(200))
+
+ // Tx0 write
+ states[0].GetOrNewStateObject(addr)
+ states[0].SetState(addr, key, val1)
+ states[0].SetBalance(addr, balance1)
+ states[0].FlushMVWriteSet()
+
+ // Tx1 write
+ states[1].SetState(addr, key, val2)
+ states[1].SetBalance(addr, balance2)
+ v := states[1].GetState(addr, key)
+ b := states[1].GetBalance(addr)
+ states[1].FlushMVWriteSet()
+
+ assert.Equal(t, val2, v)
+ assert.Equal(t, balance2, b)
+
+ // Tx2 read should get Tx1's value
+ v = states[2].GetState(addr, key)
+ b = states[2].GetBalance(addr)
+
+ assert.Equal(t, val2, v)
+ assert.Equal(t, balance2, b)
+
+ // Tx1 delete
+ for _, v := range states[1].writeMap {
+ mvhm.Delete(v.Path, 1)
+
+ states[1].writeMap = nil
+ }
+
+ // Tx2 read should get Tx0's value
+ v = states[2].GetState(addr, key)
+ b = states[2].GetBalance(addr)
+
+ assert.Equal(t, val1, v)
+ assert.Equal(t, balance1, b)
+
+ // Tx1 read should get Tx0's value
+ v = states[1].GetState(addr, key)
+ b = states[1].GetBalance(addr)
+
+ assert.Equal(t, val1, v)
+ assert.Equal(t, balance1, b)
+
+ // Tx0 delete
+ for _, v := range states[0].writeMap {
+ mvhm.Delete(v.Path, 0)
+
+ states[0].writeMap = nil
+ }
+
+ // Tx2 read again should get default vals
+ v = states[2].GetState(addr, key)
+ b = states[2].GetBalance(addr)
+
+ assert.Equal(t, common.Hash{}, v)
+ assert.Equal(t, common.Big0, b)
+}
+
+func TestMVHashMapWriteNoConflict(t *testing.T) {
+ t.Parallel()
+
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ mvhm := blockstm.MakeMVHashMap()
+ s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
+
+ states := []*StateDB{s}
+
+ // Create copies of the original state for each transition
+ for i := 1; i <= 4; i++ {
+ sCopy := s.Copy()
+ sCopy.txIndex = i
+ states = append(states, sCopy)
+ }
+
+ addr := common.HexToAddress("0x01")
+ key1 := common.HexToHash("0x01")
+ key2 := common.HexToHash("0x02")
+ val1 := common.HexToHash("0x01")
+ balance1 := new(big.Int).SetUint64(uint64(100))
+ val2 := common.HexToHash("0x02")
+
+ // Tx0 write
+ states[0].GetOrNewStateObject(addr)
+ states[0].FlushMVWriteSet()
+
+ // Tx2 write
+ states[2].SetState(addr, key2, val2)
+ states[2].FlushMVWriteSet()
+
+ // Tx1 write
+ tx1Snapshot := states[1].Snapshot()
+ states[1].SetState(addr, key1, val1)
+ states[1].SetBalance(addr, balance1)
+ states[1].FlushMVWriteSet()
+
+ // Tx1 read
+ assert.Equal(t, val1, states[1].GetState(addr, key1))
+ assert.Equal(t, balance1, states[1].GetBalance(addr))
+ // Tx1 should see empty value in key2
+ assert.Equal(t, common.Hash{}, states[1].GetState(addr, key2))
+
+ // Tx2 read
+ assert.Equal(t, val2, states[2].GetState(addr, key2))
+ // Tx2 should see values written by Tx1
+ assert.Equal(t, val1, states[2].GetState(addr, key1))
+ assert.Equal(t, balance1, states[2].GetBalance(addr))
+
+ // Tx3 read
+ assert.Equal(t, val1, states[3].GetState(addr, key1))
+ assert.Equal(t, val2, states[3].GetState(addr, key2))
+ assert.Equal(t, balance1, states[3].GetBalance(addr))
+
+ // Tx2 delete
+ for _, v := range states[2].writeMap {
+ mvhm.Delete(v.Path, 2)
+
+ states[2].writeMap = nil
+ }
+
+ assert.Equal(t, val1, states[3].GetState(addr, key1))
+ assert.Equal(t, balance1, states[3].GetBalance(addr))
+ assert.Equal(t, common.Hash{}, states[3].GetState(addr, key2))
+
+ // Tx1 revert
+ states[1].RevertToSnapshot(tx1Snapshot)
+ states[1].FlushMVWriteSet()
+
+ assert.Equal(t, common.Hash{}, states[3].GetState(addr, key1))
+ assert.Equal(t, common.Hash{}, states[3].GetState(addr, key2))
+ assert.Equal(t, common.Big0, states[3].GetBalance(addr))
+
+ // Tx1 delete
+ for _, v := range states[1].writeMap {
+ mvhm.Delete(v.Path, 1)
+
+ states[1].writeMap = nil
+ }
+
+ assert.Equal(t, common.Hash{}, states[3].GetState(addr, key1))
+ assert.Equal(t, common.Hash{}, states[3].GetState(addr, key2))
+ assert.Equal(t, common.Big0, states[3].GetBalance(addr))
+}
+
+func TestApplyMVWriteSet(t *testing.T) {
+ t.Parallel()
+
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ mvhm := blockstm.MakeMVHashMap()
+ s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
+
+ sClean := s.Copy()
+ sClean.mvHashmap = nil
+
+ sSingleProcess := sClean.Copy()
+
+ states := []*StateDB{s}
+
+ // Create copies of the original state for each transition
+ for i := 1; i <= 4; i++ {
+ sCopy := s.Copy()
+ sCopy.txIndex = i
+ states = append(states, sCopy)
+ }
+
+ addr1 := common.HexToAddress("0x01")
+ addr2 := common.HexToAddress("0x02")
+ addr3 := common.HexToAddress("0x03")
+ key1 := common.HexToHash("0x01")
+ key2 := common.HexToHash("0x02")
+ val1 := common.HexToHash("0x01")
+ balance1 := new(big.Int).SetUint64(uint64(100))
+ val2 := common.HexToHash("0x02")
+ balance2 := new(big.Int).SetUint64(uint64(200))
+ code := []byte{1, 2, 3}
+
+ // Tx0 write
+ states[0].GetOrNewStateObject(addr1)
+ states[0].SetState(addr1, key1, val1)
+ states[0].SetBalance(addr1, balance1)
+ states[0].SetState(addr2, key2, val2)
+ states[0].GetOrNewStateObject(addr3)
+ states[0].Finalise(true)
+ states[0].FlushMVWriteSet()
+
+ sSingleProcess.GetOrNewStateObject(addr1)
+ sSingleProcess.SetState(addr1, key1, val1)
+ sSingleProcess.SetBalance(addr1, balance1)
+ sSingleProcess.SetState(addr2, key2, val2)
+ sSingleProcess.GetOrNewStateObject(addr3)
+
+ sClean.ApplyMVWriteSet(states[0].MVWriteList())
+
+ assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
+
+ // Tx1 write
+ states[1].SetState(addr1, key2, val2)
+ states[1].SetBalance(addr1, balance2)
+ states[1].SetNonce(addr1, 1)
+ states[1].Finalise(true)
+ states[1].FlushMVWriteSet()
+
+ sSingleProcess.SetState(addr1, key2, val2)
+ sSingleProcess.SetBalance(addr1, balance2)
+ sSingleProcess.SetNonce(addr1, 1)
+
+ sClean.ApplyMVWriteSet(states[1].MVWriteList())
+
+ assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
+
+ // Tx2 write
+ states[2].SetState(addr1, key1, val2)
+ states[2].SetBalance(addr1, balance2)
+ states[2].SetNonce(addr1, 2)
+ states[2].Finalise(true)
+ states[2].FlushMVWriteSet()
+
+ sSingleProcess.SetState(addr1, key1, val2)
+ sSingleProcess.SetBalance(addr1, balance2)
+ sSingleProcess.SetNonce(addr1, 2)
+
+ sClean.ApplyMVWriteSet(states[2].MVWriteList())
+
+ assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
+
+ // Tx3 write
+ states[3].Suicide(addr2)
+ states[3].SetCode(addr1, code)
+ states[3].Finalise(true)
+ states[3].FlushMVWriteSet()
+
+ sSingleProcess.Suicide(addr2)
+ sSingleProcess.SetCode(addr1, code)
+
+ sClean.ApplyMVWriteSet(states[3].MVWriteList())
+
+ assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
+}
+
// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy.
// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512
func TestCopyOfCopy(t *testing.T) {
diff --git a/core/state_processor.go b/core/state_processor.go
index e4c775ec13..a02bfea9ec 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -57,7 +57,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
-func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
+func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, interruptCtx context.Context) (types.Receipts, []*types.Log, uint64, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
@@ -75,12 +75,19 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
+ if interruptCtx != nil {
+ select {
+ case <-interruptCtx.Done():
+ return nil, nil, 0, interruptCtx.Err()
+ default:
+ }
+ }
msg, err := TransactionToMessage(tx, types.MakeSigner(p.config, header.Number), header.BaseFee)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.SetTxContext(tx.Hash(), i)
- receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, nil)
+ receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, interruptCtx)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
@@ -89,6 +96,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
}
// Fail if Shanghai not enabled and len(withdrawals) is non-zero.
withdrawals := block.Withdrawals()
+ // TODO marcello IsShanghai
if len(withdrawals) > 0 && !p.config.IsShanghai(block.Time()) {
return nil, nil, 0, fmt.Errorf("withdrawals before shanghai")
}
@@ -104,12 +112,51 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)
- // Apply the transaction to the current state (included in the env).
- result, err := ApplyMessage(evm, msg, gp, interruptCtx)
+ var result *ExecutionResult
+
+ var err error
+
+ backupMVHashMap := statedb.GetMVHashmap()
+
+ // pause recording read and write
+ statedb.SetMVHashmap(nil)
+
+ coinbaseBalance := statedb.GetBalance(evm.Context.Coinbase)
+
+ // resume recording read and write
+ statedb.SetMVHashmap(backupMVHashMap)
+
+ result, err = ApplyMessageNoFeeBurnOrTip(evm, *msg, gp, interruptCtx)
if err != nil {
return nil, err
}
+ // stop recording read and write
+ statedb.SetMVHashmap(nil)
+
+ if evm.ChainConfig().IsLondon(blockNumber) {
+ statedb.AddBalance(result.BurntContractAddress, result.FeeBurnt)
+ }
+
+ statedb.AddBalance(evm.Context.Coinbase, result.FeeTipped)
+ output1 := new(big.Int).SetBytes(result.SenderInitBalance.Bytes())
+ output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes())
+
+ // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
+ // add transfer log
+ AddFeeTransferLog(
+ statedb,
+
+ msg.From,
+ evm.Context.Coinbase,
+
+ result.FeeTipped,
+ result.SenderInitBalance,
+ coinbaseBalance,
+ output1.Sub(output1, result.FeeTipped),
+ output2.Add(output2, result.FeeTipped),
+ )
+
if result.Err == vm.ErrInterrupt {
return nil, result.Err
}
diff --git a/core/state_processor_test.go b/core/state_processor_test.go
index 5a2589fb85..0865c32a4b 100644
--- a/core/state_processor_test.go
+++ b/core/state_processor_test.go
@@ -247,27 +247,32 @@ func TestStateProcessorErrors(t *testing.T) {
},
},
}
- blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
+ blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
+ parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
)
defer blockchain.Stop()
- for i, tt := range []struct {
- txs []*types.Transaction
- want string
- }{
- { // ErrTxTypeNotSupported
- txs: []*types.Transaction{
- mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
+ defer parallelBlockchain.Stop()
+
+ for _, bc := range []*BlockChain{blockchain, parallelBlockchain} {
+ for i, tt := range []struct {
+ txs []*types.Transaction
+ want string
+ }{
+ { // ErrTxTypeNotSupported
+ txs: []*types.Transaction{
+ mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
+ },
+ want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported",
},
- want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported",
- },
- } {
- block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config)
- _, err := blockchain.InsertChain(types.Blocks{block})
- if err == nil {
- t.Fatal("block imported without errors")
- }
- if have, want := err.Error(), tt.want; have != want {
- t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
+ } {
+ block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config)
+ _, err := bc.InsertChain(types.Blocks{block})
+ if err == nil {
+ t.Fatal("block imported without errors")
+ }
+ if have, want := err.Error(), tt.want; have != want {
+ t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
+ }
}
}
}
@@ -286,27 +291,32 @@ func TestStateProcessorErrors(t *testing.T) {
},
},
}
- blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
+ blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
+ parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
)
defer blockchain.Stop()
- for i, tt := range []struct {
- txs []*types.Transaction
- want string
- }{
- { // ErrSenderNoEOA
- txs: []*types.Transaction{
- mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
+ defer parallelBlockchain.Stop()
+
+ for _, bc := range []*BlockChain{blockchain, parallelBlockchain} {
+ for i, tt := range []struct {
+ txs []*types.Transaction
+ want string
+ }{
+ { // ErrSenderNoEOA
+ txs: []*types.Transaction{
+ mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
+ },
+ want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1",
},
- want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1",
- },
- } {
- block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config)
- _, err := blockchain.InsertChain(types.Blocks{block})
- if err == nil {
- t.Fatal("block imported without errors")
- }
- if have, want := err.Error(), tt.want; have != want {
- t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
+ } {
+ block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config)
+ _, err := bc.InsertChain(types.Blocks{block})
+ if err == nil {
+ t.Fatal("block imported without errors")
+ }
+ if have, want := err.Error(), tt.want; have != want {
+ t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
+ }
}
}
}
@@ -344,36 +354,42 @@ func TestStateProcessorErrors(t *testing.T) {
},
},
}
- genesis = gspec.MustCommit(db)
- blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil, nil)
- tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
- smallInitCode = [320]byte{}
+ genesis = gspec.MustCommit(db)
+ blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil, nil)
+ parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
+ tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
+ smallInitCode = [320]byte{}
)
defer blockchain.Stop()
- for i, tt := range []struct {
- txs []*types.Transaction
- want string
- }{
- { // ErrMaxInitCodeSizeExceeded
- txs: []*types.Transaction{
- mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header()), tooBigInitCode[:]),
+ defer parallelBlockchain.Stop()
+
+ for _, bc := range []*BlockChain{blockchain, parallelBlockchain} {
+
+ for i, tt := range []struct {
+ txs []*types.Transaction
+ want string
+ }{
+ { // ErrMaxInitCodeSizeExceeded
+ txs: []*types.Transaction{
+ mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header()), tooBigInitCode[:]),
+ },
+ want: "could not apply tx 0 [0x832b54a6c3359474a9f504b1003b2cc1b6fcaa18e4ef369eb45b5d40dad6378f]: max initcode size exceeded: code size 49153 limit 49152",
},
- want: "could not apply tx 0 [0x832b54a6c3359474a9f504b1003b2cc1b6fcaa18e4ef369eb45b5d40dad6378f]: max initcode size exceeded: code size 49153 limit 49152",
- },
- { // ErrIntrinsicGas: Not enough gas to cover init code
- txs: []*types.Transaction{
- mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header()), smallInitCode[:]),
+ { // ErrIntrinsicGas: Not enough gas to cover init code
+ txs: []*types.Transaction{
+ mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header()), smallInitCode[:]),
+ },
+ want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300",
},
- want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300",
- },
- } {
- block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config)
- _, err := blockchain.InsertChain(types.Blocks{block})
- if err == nil {
- t.Fatal("block imported without errors")
- }
- if have, want := err.Error(), tt.want; have != want {
- t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
+ } {
+ block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config)
+ _, err := bc.InsertChain(types.Blocks{block})
+ if err == nil {
+ t.Fatal("block imported without errors")
+ }
+ if have, want := err.Error(), tt.want; have != want {
+ t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
+ }
}
}
}
diff --git a/core/state_transition.go b/core/state_transition.go
index 43c4422921..a4e3212349 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -35,6 +35,10 @@ type ExecutionResult struct {
UsedGas uint64 // Total used gas but include the refunded gas
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
+ SenderInitBalance *big.Int
+ FeeBurnt *big.Int
+ BurntContractAddress common.Address
+ FeeTipped *big.Int
}
// Unwrap returns the internal evm error which allows us for further
@@ -177,6 +181,13 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool, interruptCtx context.C
return NewStateTransition(evm, msg, gp).TransitionDb(interruptCtx)
}
+func ApplyMessageNoFeeBurnOrTip(evm *vm.EVM, msg Message, gp *GasPool, interruptCtx context.Context) (*ExecutionResult, error) {
+ st := NewStateTransition(evm, msg, gp)
+ st.noFeeBurnAndTip = true
+
+ return st.TransitionDb(interruptCtx)
+}
+
// StateTransition represents a state transition.
//
// == The State Transitioning Model
@@ -311,8 +322,10 @@ func (st *StateTransition) preCheck() error {
// nil evm execution result.
func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*ExecutionResult, error) {
input1 := st.state.GetBalance(st.msg.From)
- input2 := st.state.GetBalance(st.evm.Context.Coinbase)
-
+ var input2 *big.Int
+ if !st.noFeeBurnAndTip {
+ input2 := st.state.GetBalance(st.evm.Context.Coinbase)
+ }
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
@@ -404,34 +417,47 @@ func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*Executio
amount := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)
+ var burnAmount *big.Int
+
+ var burntContractAddress common.Address
+
if rules.IsLondon {
burntContractAddress := common.HexToAddress(st.evm.ChainConfig().Bor.CalculateBurntContract(st.evm.Context.BlockNumber.Uint64()))
burnAmount := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)
- st.state.AddBalance(burntContractAddress, burnAmount)
+ if !st.noFeeBurnAndTip {
+ st.state.AddBalance(burntContractAddress, burnAmount)
+ }
}
- st.state.AddBalance(st.evm.Context.Coinbase, amount)
- output1 := new(big.Int).SetBytes(input1.Bytes())
- output2 := new(big.Int).SetBytes(input2.Bytes())
+ if !st.noFeeBurnAndTip {
+ st.state.AddBalance(st.evm.Context.Coinbase, amount)
+ output1 := new(big.Int).SetBytes(input1.Bytes())
+ output2 := new(big.Int).SetBytes(input2.Bytes())
- // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
- // add transfer log
- AddFeeTransferLog(
- st.state,
+ // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
+ // add transfer log
+ AddFeeTransferLog(
+ st.state,
- msg.From,
- st.evm.Context.Coinbase,
+ msg.From,
+ st.evm.Context.Coinbase,
+
+ amount,
+ input1,
+ input2,
+ output1.Sub(output1, amount),
+ output2.Add(output2, amount),
+ )
+ }
- amount,
- input1,
- input2,
- output1.Sub(output1, amount),
- output2.Add(output2, amount),
- )
return &ExecutionResult{
UsedGas: st.gasUsed(),
Err: vmerr,
ReturnData: ret,
+ SenderInitBalance: input1,
+ FeeBurnt: burnAmount,
+ BurntContractAddress: burntContractAddress,
+ FeeTipped: amount,
}, nil
}
diff --git a/core/types.go b/core/types.go
index 36eb0d1ded..1bb04fbd75 100644
--- a/core/types.go
+++ b/core/types.go
@@ -17,6 +17,7 @@
package core
import (
+ "context"
"sync/atomic"
"github.com/ethereum/go-ethereum/core/state"
@@ -49,5 +50,5 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
- Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error)
+ Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, interruptCtx context.Context) (types.Receipts, []*types.Log, uint64, error)
}
diff --git a/core/types/block.go b/core/types/block.go
index a1a14f5b16..6d9a8d29ed 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -88,6 +88,11 @@ type Header struct {
// ExcessDataGas was added by EIP-4844 and is ignored in legacy headers.
ExcessDataGas *big.Int `json:"excessDataGas" rlp:"optional"`
+ // length of TxDependency -> n (n = number of transactions in the block)
+ // length of TxDependency[i] -> k (k = a whole number)
+ // k elements in TxDependency[i] -> transaction indexes on which transaction i is dependent on
+ TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
+
/*
TODO (MariusVanDerWijden) Add this field once needed
// Random was added during the merge and contains the BeaconState randomness
@@ -285,6 +290,15 @@ func CopyHeader(h *Header) *Header {
cpy.WithdrawalsHash = new(common.Hash)
*cpy.WithdrawalsHash = *h.WithdrawalsHash
}
+
+ if len(h.TxDependency) > 0 {
+ cpy.TxDependency = make([][]uint64, len(h.TxDependency))
+
+ for i, dep := range h.TxDependency {
+ cpy.TxDependency[i] = make([]uint64, len(dep))
+ copy(cpy.TxDependency[i], dep)
+ }
+ }
return &cpy
}
@@ -341,6 +355,7 @@ func (b *Block) TxHash() common.Hash { return b.header.TxHash }
func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash }
func (b *Block) UncleHash() common.Hash { return b.header.UncleHash }
func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) }
+func (b *Block) TxDependency() [][]uint64 { return b.header.TxDependency }
func (b *Block) BaseFee() *big.Int {
if b.header.BaseFee == nil {
diff --git a/core/types/block_test.go b/core/types/block_test.go
index 966015eb01..672e285840 100644
--- a/core/types/block_test.go
+++ b/core/types/block_test.go
@@ -68,6 +68,51 @@ func TestBlockEncoding(t *testing.T) {
}
}
+func TestTxDependencyBlockEncoding(t *testing.T) {
+ t.Parallel()
+
+ blockEnc := common.FromHex("f90268f90201a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c480c6c20201c20180f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0")
+
+ var block Block
+
+ if err := rlp.DecodeBytes(blockEnc, &block); err != nil {
+ t.Fatal("decode error: ", err)
+ }
+
+ check := func(f string, got, want interface{}) {
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("%s mismatch: got %v, want %v", f, got, want)
+ }
+ }
+
+ check("Difficulty", block.Difficulty(), big.NewInt(131072))
+ check("GasLimit", block.GasLimit(), uint64(3141592))
+ check("GasUsed", block.GasUsed(), uint64(21000))
+ check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"))
+ check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
+ check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
+ check("Hash", block.Hash(), common.HexToHash("0xc6d8dc8995c0a4374bb9f87bd0dd8c0761e6e026a71edbfed5e961c9e55dbd6a"))
+ check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
+ check("Time", block.Time(), uint64(1426516743))
+ check("Size", block.Size(), common.StorageSize(len(blockEnc)))
+ check("TxDependency", block.TxDependency(), [][]uint64{{2, 1}, {1, 0}})
+
+ tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil)
+ tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100"))
+
+ check("len(Transactions)", len(block.Transactions()), 1)
+ check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash())
+ ourBlockEnc, err := rlp.EncodeToBytes(&block)
+
+ if err != nil {
+ t.Fatal("encode error: ", err)
+ }
+
+ if !bytes.Equal(ourBlockEnc, blockEnc) {
+ t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc)
+ }
+}
+
func TestEIP1559BlockEncoding(t *testing.T) {
blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0")
var block Block
diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go
index 5c8b81652d..61b7f128ca 100644
--- a/core/types/gen_header_json.go
+++ b/core/types/gen_header_json.go
@@ -33,6 +33,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
Nonce BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
+ TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
Hash common.Hash `json:"hash"`
}
var enc Header
@@ -53,6 +54,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.Nonce = h.Nonce
enc.BaseFee = (*hexutil.Big)(h.BaseFee)
enc.WithdrawalsHash = h.WithdrawalsHash
+ enc.TxDependency = h.TxDependency
enc.Hash = h.Hash()
return json.Marshal(&enc)
}
@@ -77,6 +79,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
Nonce *BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
+ TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
}
var dec Header
if err := json.Unmarshal(input, &dec); err != nil {
@@ -145,5 +148,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.WithdrawalsHash != nil {
h.WithdrawalsHash = dec.WithdrawalsHash
}
+ if dec.TxDependency != nil {
+ h.TxDependency = dec.TxDependency
+ }
return nil
}
diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go
index 7fd2cf8f2d..7d00a78ccc 100644
--- a/core/types/gen_header_rlp.go
+++ b/core/types/gen_header_rlp.go
@@ -42,7 +42,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBytes(obj.Nonce[:])
_tmp1 := obj.BaseFee != nil
_tmp2 := obj.WithdrawalsHash != nil
- if _tmp1 || _tmp2 {
+ _tmp3 := len(obj.TxDependency) > 0
+ if _tmp1 || _tmp2 || _tmp3 {
if obj.BaseFee == nil {
w.Write(rlp.EmptyString)
} else {
@@ -59,6 +60,17 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBytes(obj.WithdrawalsHash[:])
}
}
+ if _tmp3 {
+ _tmp3 := w.List()
+ for _, _tmp4 := range obj.TxDependency {
+ _tmp5 := w.List()
+ for _, _tmp6 := range _tmp4 {
+ w.WriteUint64(_tmp6)
+ }
+ w.ListEnd(_tmp5)
+ }
+ w.ListEnd(_tmp3)
+ }
w.ListEnd(_tmp0)
return w.Flush()
}
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 2e05cee976..0e99230009 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -52,6 +52,10 @@ type Config struct {
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled
+
+ // parallel EVM configs
+ ParallelEnable bool
+ ParallelSpeculativeProcesses int
}
// ScopeContext contains the things that are per-call, such as stack and memory,
@@ -265,6 +269,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i
txHash, _ := GetCurrentTxFromContext(interruptCtx)
interruptedTxCache, _ := GetCache(interruptCtx)
+ if interruptedTxCache == nil {
+ break
+ }
+
// if the tx is already in the cache, it means that it has been interrupted before and we will not interrupt it again
found, _ := interruptedTxCache.Cache.ContainsOrAdd(txHash, true)
if found {
@@ -425,6 +433,11 @@ func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnl
case <-interruptCtx.Done():
txHash, _ := GetCurrentTxFromContext(interruptCtx)
interruptedTxCache, _ := GetCache(interruptCtx)
+
+ if interruptedTxCache == nil {
+ break
+ }
+
// if the tx is already in the cache, it means that it has been interrupted before and we will not interrupt it again
found, _ := interruptedTxCache.Cache.ContainsOrAdd(txHash, true)
log.Info("FOUND", "found", found, "txHash", txHash)
diff --git a/docs/cli/example_config.toml b/docs/cli/example_config.toml
index bffbd77631..f62fcfebdb 100644
--- a/docs/cli/example_config.toml
+++ b/docs/cli/example_config.toml
@@ -35,9 +35,9 @@ devfakeauthor = false # Run miner without validator set authorization
port = 30303 # Network listening port
nodiscover = false # Disables the peer discovery mechanism (manual peer addition)
nat = "any" # NAT port mapping mechanism (any|none|upnp|pmp|extip:)
- netrestrict = "" # Restricts network communication to the given IP networks (CIDR masks)
- nodekey = "" # P2P node key file
- nodekeyhex = "" # P2P node key as hex
+ netrestrict = "" # Restricts network communication to the given IP networks (CIDR masks)
+ nodekey = "" # P2P node key file
+ nodekeyhex = "" # P2P node key as hex
txarrivalwait = "500ms" # Maximum duration to wait before requesting an announced transaction
[p2p.discovery]
v5disc = false # Enables the experimental RLPx V5 (Topic Discovery) mechanism
@@ -73,6 +73,7 @@ devfakeauthor = false # Run miner without validator set authorization
gaslimit = 30000000 # Target gas ceiling for mined blocks
gasprice = "1000000000" # Minimum gas price for mining a transaction (recommended for mainnet = 30000000000, default suitable for mumbai/devnet)
recommit = "2m5s" # The time interval for miner to re-create mining work
+ commitinterrupt = true # Interrupt the current mining work when time is exceeded and create partial blocks
[jsonrpc]
ipcdisable = false # Disable the IPC-RPC server
@@ -129,7 +130,7 @@ devfakeauthor = false # Run miner without validator set authorization
metrics = false # Enable metrics collection and reporting
expensive = false # Enable expensive metrics collection and reporting
prometheus-addr = "127.0.0.1:7071" # Address for Prometheus Server
- opencollector-endpoint = "127.0.0.1:4317" # OpenCollector Endpoint (host:port)
+ opencollector-endpoint = "" # OpenCollector Endpoint (host:port)
[telemetry.influx]
influxdb = false # Enable metrics export/push to an external InfluxDB database (v1)
endpoint = "" # InfluxDB API endpoint to report metrics to
diff --git a/docs/cli/server.md b/docs/cli/server.md
index 2f049ec210..d7a9a1296e 100644
--- a/docs/cli/server.md
+++ b/docs/cli/server.md
@@ -72,6 +72,10 @@ The ```bor server``` command runs the Bor client.
- ```dev.period```: Block period to use in developer mode (0 = mine only if transaction pending) (default: 0)
+- ```parallelevm.enable```: Enable Block STM (default: true)
+
+- ```parallelevm.procs```: Number of speculative processes (cores) in Block STM (default: 8)
+
- ```dev.gaslimit```: Initial block gas limit (default: 11500000)
- ```pprof```: Enable the pprof HTTP server (default: false)
@@ -216,7 +220,7 @@ The ```bor server``` command runs the Bor client.
- ```v5disc```: Enables the experimental RLPx V5 (Topic Discovery) mechanism (default: false)
-- ```txarrivalwait```: Maximum duration to wait before requesting an announced transaction (default: 500ms)
+- ```txarrivalwait```: Maximum duration to wait for a transaction before explicitly requesting it (defaults to 500ms) (default: 500ms)
### Sealer Options
@@ -232,6 +236,8 @@ The ```bor server``` command runs the Bor client.
- ```miner.recommit```: The time interval for miner to re-create mining work (default: 2m5s)
+- ```miner.interruptcommit```: Interrupt block commit when block creation time is passed (default: true)
+
### Telemetry Options
- ```metrics```: Enable metrics collection and reporting (default: false)
@@ -284,4 +290,4 @@ The ```bor server``` command runs the Bor client.
- ```txpool.globalqueue```: Maximum number of non-executable transaction slots for all accounts (default: 32768)
-- ```txpool.lifetime```: Maximum amount of time non-executable transaction are queued (default: 3h0m0s)
\ No newline at end of file
+- ```txpool.lifetime```: Maximum amount of time non-executable transaction are queued (default: 3h0m0s)
diff --git a/eth/backend.go b/eth/backend.go
index ddadae5433..80cf7f01b2 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -111,7 +111,6 @@ type Ethereum struct {
// New creates a new Ethereum object (including the
// initialisation of the common Ethereum object)
-// TODO fixme marcello c'é qcls che non va qua (CreateConsensusEngine chiamata due volte)
func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Ensure configuration values are compatible and sane
if config.SyncMode == downloader.LightSync {
@@ -207,7 +206,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
var (
vmConfig = vm.Config{
- EnablePreimageRecording: config.EnablePreimageRecording,
+ EnablePreimageRecording: config.EnablePreimageRecording,
+ ParallelEnable: config.ParallelEVM.Enable,
+ ParallelSpeculativeProcesses: config.ParallelEVM.SpeculativeProcesses,
}
cacheConfig = &core.CacheConfig{
TrieCleanLimit: config.TrieCleanCache,
@@ -230,7 +231,15 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
checker := whitelist.NewService(10)
- ethereum.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, ethereum.engine, vmConfig, ethereum.shouldPreserve, &config.TxLookupLimit, checker)
+ // check if Parallel EVM is enabled
+ // if enabled, use parallel state processor
+ if config.ParallelEVM.Enable {
+ // TODO marcello fix NewParallelBlockChain
+ ethereum.blockchain, err = core.NewParallelBlockChain(chainDb, cacheConfig, config.Genesis, ethereum.engine, vmConfig, ethereum.shouldPreserve, &config.TxLookupLimit, checker)
+ } else {
+ ethereum.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, ethereum.engine, vmConfig, ethereum.shouldPreserve, &config.TxLookupLimit, checker)
+ }
+
if err != nil {
return nil, err
}
diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go
index 5d63b75996..1fbd0287bc 100644
--- a/eth/downloader/downloader_test.go
+++ b/eth/downloader/downloader_test.go
@@ -1608,9 +1608,9 @@ func TestFakedSyncProgress66NoRemoteCheckpoint(t *testing.T) {
tester := newTester(t)
validate := func(count int) (bool, error) {
- // only return the `ErrNoRemoteCheckoint` error for the first call
+ // only return the `ErrNoRemoteCheckpoint` error for the first call
if count == 0 {
- return false, whitelist.ErrNoRemoteCheckoint
+ return false, whitelist.ErrNoRemoteCheckpoint
}
return true, nil
@@ -1626,7 +1626,7 @@ func TestFakedSyncProgress66NoRemoteCheckpoint(t *testing.T) {
// Synchronise with the peer and make sure all blocks were retrieved
// Should fail in first attempt
if err := tester.sync("light", nil, mode); err != nil {
- assert.Equal(t, whitelist.ErrNoRemoteCheckoint, err, "failed synchronisation")
+ assert.Equal(t, whitelist.ErrNoRemoteCheckpoint, err, "failed synchronisation")
}
// Try syncing again, should succeed
diff --git a/eth/downloader/whitelist/service.go b/eth/downloader/whitelist/service.go
index a2edcfb797..5d3fe477d3 100644
--- a/eth/downloader/whitelist/service.go
+++ b/eth/downloader/whitelist/service.go
@@ -31,7 +31,7 @@ func NewService(maxCapacity uint) *Service {
var (
ErrCheckpointMismatch = errors.New("checkpoint mismatch")
ErrLongFutureChain = errors.New("received future chain of unacceptable length")
- ErrNoRemoteCheckoint = errors.New("remote peer doesn't have a checkoint")
+ ErrNoRemoteCheckpoint = errors.New("remote peer doesn't have a checkpoint")
)
// IsValidPeer checks if the chain we're about to receive from a peer is valid or not
@@ -55,11 +55,11 @@ func (w *Service) IsValidPeer(remoteHeader *types.Header, fetchHeadersByNumber f
// todo: we can extract this as an interface and mock as well or just test IsValidChain in isolation from downloader passing fake fetchHeadersByNumber functions
headers, hashes, err := fetchHeadersByNumber(lastCheckpointBlockNum, 1, 0, false)
if err != nil {
- return false, fmt.Errorf("%w: last checkpoint %d, err %v", ErrNoRemoteCheckoint, lastCheckpointBlockNum, err)
+ return false, fmt.Errorf("%w: last checkpoint %d, err %v", ErrNoRemoteCheckpoint, lastCheckpointBlockNum, err)
}
if len(headers) == 0 {
- return false, fmt.Errorf("%w: last checkpoint %d", ErrNoRemoteCheckoint, lastCheckpointBlockNum)
+ return false, fmt.Errorf("%w: last checkpoint %d", ErrNoRemoteCheckpoint, lastCheckpointBlockNum)
}
reqBlockNum := headers[0].Number.Uint64()
diff --git a/eth/downloader/whitelist/service_test.go b/eth/downloader/whitelist/service_test.go
index a91148c761..cf0b076cfd 100644
--- a/eth/downloader/whitelist/service_test.go
+++ b/eth/downloader/whitelist/service_test.go
@@ -64,14 +64,14 @@ func TestIsValidPeer(t *testing.T) {
}
// case2: false fetchHeadersByNumber function provided, should consider the chain as invalid
- // and throw `ErrNoRemoteCheckoint` error
+ // and throw `ErrNoRemoteCheckpoint` error
res, err = s.IsValidPeer(nil, falseFetchHeadersByNumber)
if err == nil {
t.Fatal("expected error, got nil")
}
- if !errors.Is(err, ErrNoRemoteCheckoint) {
- t.Fatalf("expected error ErrNoRemoteCheckoint, got %v", err)
+ if !errors.Is(err, ErrNoRemoteCheckpoint) {
+ t.Fatalf("expected error ErrNoRemoteCheckpoint, got %v", err)
}
require.Equal(t, res, false, "expected chain to be invalid")
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 08101e3420..e8a1725a94 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -18,7 +18,7 @@
package ethconfig
import (
- "github.com/ethereum/go-ethereum/internal/ethapi"
+ "math/big"
"os"
"os/user"
"path/filepath"
@@ -41,6 +41,7 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
@@ -242,6 +243,9 @@ type Config struct {
// Bor logs flag
BorLogs bool
+ // Parallel EVM (Block-STM) related config
+ ParallelEVM core.ParallelEVMConfig `toml:",omitempty"`
+
// Develop Fake Author mode to produce blocks without authorisation
DevFakeAuthor bool `hcl:"devfakeauthor,optional" toml:"devfakeauthor,optional"`
}
diff --git a/eth/protocols/eth/dispatcher.go b/eth/protocols/eth/dispatcher.go
index 3f81e045ba..0b16060978 100644
--- a/eth/protocols/eth/dispatcher.go
+++ b/eth/protocols/eth/dispatcher.go
@@ -212,6 +212,7 @@ func (p *Peer) dispatcher() {
}
// Stop tracking the request
delete(pending, cancelOp.id)
+ requestTracker.Fulfil(p.id, p.version, req.code, cancelOp.id)
cancelOp.fail <- nil
case resOp := <-p.resDispatch:
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index a3d2d70682..55d2ffe914 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -159,7 +159,9 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, nil, fmt.Errorf("block #%d not found", next)
}
- _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
+
+ _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}, nil)
+
if err != nil {
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
}
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 9f5f8c54a5..0261b6b04d 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -20,10 +20,14 @@ import (
"bufio"
"bytes"
"context"
+ "encoding/hex"
"encoding/json"
"errors"
"fmt"
+ "io/ioutil"
+ "math/big"
"os"
+ "path/filepath"
"runtime"
"sync"
"time"
@@ -68,6 +72,10 @@ const (
// for tracing. The creation of trace state will be paused if the unused
// trace states exceed this limit.
maximumPendingTraceStates = 128
+
+ defaultPath = string(".")
+
+ defaultIOFlag = false
)
var defaultBorTraceEnabled = newBoolPtr(false)
@@ -78,6 +86,8 @@ var errTxNotFound = errors.New("transaction not found")
// historical state for tracing purposes.
type StateReleaseFunc func()
+var allowIOTracing = false // Change this to true to enable IO tracing for debugging
+
// Backend interface provides the common API services (that are provided by
// both full and light clients) with access to necessary functions.
type Backend interface {
@@ -204,9 +214,11 @@ type TraceConfig struct {
Tracer *string
Timeout *string
Reexec *uint64
+ Path *string
+ IOFlag *bool
// Config specific to given tracer. Note struct logger
// config are historically embedded in main object.
- TracerConfig json.RawMessage
+ TracerConfig json.RawMessage
BorTraceEnabled *bool
BorTx *bool
}
@@ -222,8 +234,8 @@ type TraceCallConfig struct {
// StdTraceConfig holds extra parameters to standard-json trace functions.
type StdTraceConfig struct {
logger.Config
- Reexec *uint64
- TxHash common.Hash
+ Reexec *uint64
+ TxHash common.Hash
BorTraceEnabled *bool
}
@@ -691,7 +703,6 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
return roots, nil
}
-
// StandardTraceBadBlockToFile dumps the structured logs created during the
// execution of EVM against a block pulled from the pool of bad ones to the
// local file system and returns a list of files to the caller.
@@ -734,19 +745,36 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
+
+ path := defaultPath
+ if config != nil && config.Path != nil {
+ path = *config.Path
+ }
+
+ ioflag := defaultIOFlag
+ if allowIOTracing && config != nil && config.IOFlag != nil {
+ ioflag = *config.IOFlag
+ }
+
statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil {
return nil, err
}
defer release()
+ // create and add empty mvHashMap in statedb as StateAtBlock does not have mvHashmap in it.
+ if ioflag {
+ statedb.AddEmptyMVHashMap()
+ }
+
+ // Execute all the transaction contained within the block concurrently
var (
txs, stateSyncPresent = api.getAllBlockTransactions(ctx, block)
- blockHash = block.Hash()
- blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
- signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
- results = make([]*txTraceResult, len(txs))
- pend sync.WaitGroup
+ blockHash = block.Hash()
+ blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
+ signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
+ results = make([]*txTraceResult, len(txs))
+ pend sync.WaitGroup
)
threads := runtime.NumCPU()
if threads > len(txs) {
@@ -790,10 +818,30 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
}()
}
+ var IOdump string
+
+ var RWstruct []state.DumpStruct
+
+ var london bool
+
+ if ioflag {
+ IOdump = "TransactionIndex, Incarnation, VersionTxIdx, VersionInc, Path, Operation\n"
+ RWstruct = []state.DumpStruct{}
+ }
// Feed the transactions into the tracers and return
var failed error
+
+ if ioflag {
+ london = api.backend.ChainConfig().IsLondon(block.Number())
+ }
+
txloop:
for i, tx := range txs {
+ if ioflag {
+ // copy of statedb
+ statedb = statedb.Copy()
+ }
+
// Send the trace task over for execution
task := &txTraceTask{statedb: statedb.Copy(), index: i}
select {
@@ -807,27 +855,88 @@ txloop:
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
statedb.SetTxContext(tx.Hash(), i)
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
- //nolint: nestif
- if stateSyncPresent && i == len(txs)-1 {
- if *config.BorTraceEnabled {
- callmsg := prepareCallMessage(*msg)
- if _, err := statefull.ApplyBorMessage(*vmenv, callmsg); err != nil {
- failed = err
+
+ // nolint: nestif
+ if !ioflag {
+ //nolint: nestif
+ if stateSyncPresent && i == len(txs)-1 {
+ if *config.BorTraceEnabled {
+ callmsg := prepareCallMessage(*msg)
+ // nolint : contextcheck
+ if _, err := statefull.ApplyBorMessage(*vmenv, callmsg); err != nil {
+ failed = err
+ break txloop
+ }
+ } else {
break txloop
}
} else {
- break txloop
+ // nolint : contextcheck
+ if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), context.Background()); err != nil {
+ failed = err
+ break txloop
+ }
+ // Finalize the state so any modifications are written to the trie
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
}
} else {
+ coinbaseBalance := statedb.GetBalance(blockCtx.Coinbase)
// nolint : contextcheck
- if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), context.Background()); err != nil {
+ result, err := core.ApplyMessageNoFeeBurnOrTip(vmenv, *msg, new(core.GasPool).AddGas(msg.GasLimit), context.Background())
+
+ if err != nil {
failed = err
- break txloop
+ break
}
+
+ if london {
+ statedb.AddBalance(result.BurntContractAddress, result.FeeBurnt)
+ }
+
+ statedb.AddBalance(blockCtx.Coinbase, result.FeeTipped)
+ output1 := new(big.Int).SetBytes(result.SenderInitBalance.Bytes())
+ output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes())
+
+ // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
+ // add transfer log
+ core.AddFeeTransferLog(
+ statedb,
+
+ msg.From,
+ blockCtx.Coinbase,
+
+ result.FeeTipped,
+ result.SenderInitBalance,
+ coinbaseBalance,
+ output1.Sub(output1, result.FeeTipped),
+ output2.Add(output2, result.FeeTipped),
+ )
+
+ // Finalize the state so any modifications are written to the trie
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+ statedb.FlushMVWriteSet()
+
+ structRead := statedb.GetReadMapDump()
+ structWrite := statedb.GetWriteMapDump()
+
+ RWstruct = append(RWstruct, structRead...)
+ RWstruct = append(RWstruct, structWrite...)
}
- // Finalize the state so any modifications are written to the trie
- // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
- statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+ }
+
+ if ioflag {
+ for _, val := range RWstruct {
+ IOdump += fmt.Sprintf("%v , %v, %v , %v, ", val.TxIdx, val.TxInc, val.VerIdx, val.VerInc) + hex.EncodeToString(val.Path) + ", " + val.Op
+ }
+
+ // make sure that the file exists and write IOdump
+ err = ioutil.WriteFile(filepath.Join(path, "data.csv"), []byte(fmt.Sprint(IOdump)), 0600)
+ if err != nil {
+ return nil, err
+ }
+
}
close(jobs)
@@ -845,7 +954,6 @@ txloop:
}
}
-
// standardTraceBlockToFile configures a new tracer which uses standard JSON output,
// and traces either a full block or an individual transaction. The return value will
// be one filename per transaction traced.
@@ -1177,7 +1285,7 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
} else {
// nolint : contextcheck
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), context.Background()); err != nil {
- return nil, fmt.Errorf("tracing failed: %w", err)
+ return nil, fmt.Errorf("tracing failed: %w", err)
}
}
return tracer.GetResult()
diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go
index ec6f1dfd72..130ff77fbe 100644
--- a/eth/tracers/api_test.go
+++ b/eth/tracers/api_test.go
@@ -460,6 +460,85 @@ func TestTraceBlock(t *testing.T) {
}
}
+func TestIOdump(t *testing.T) {
+ t.Parallel()
+
+ // Initialize test accounts
+ accounts := newAccounts(5)
+ genesis := &core.Genesis{Alloc: core.GenesisAlloc{
+ accounts[0].addr: {Balance: big.NewInt(params.Ether)},
+ accounts[1].addr: {Balance: big.NewInt(params.Ether)},
+ accounts[2].addr: {Balance: big.NewInt(params.Ether)},
+ accounts[3].addr: {Balance: big.NewInt(params.Ether)},
+ accounts[4].addr: {Balance: big.NewInt(params.Ether)},
+ }}
+ genBlocks := 1
+ signer := types.HomesteadSigner{}
+ api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
+ // Transfer from account[0] to account[1], account[1] to account[2], account[2] to account[3], account[3] to account[4], account[4] to account[0]
+ // value: 1000 wei
+ // fee: 0 wei
+
+ for j := 0; j < 5; j++ {
+ tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[(j+1)%5].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[j].key)
+ b.AddTx(tx)
+ }
+ }))
+
+ allowIOTracing = true
+
+ ioflag := new(bool)
+
+ *ioflag = true
+
+ var testSuite = []struct {
+ blockNumber rpc.BlockNumber
+ config *TraceConfig
+ want string
+ expectErr error
+ }{
+ // Trace head block
+ {
+ config: &TraceConfig{
+ IOFlag: ioflag,
+ },
+ blockNumber: rpc.BlockNumber(genBlocks),
+ want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`,
+ },
+ }
+
+ for i, tc := range testSuite {
+ result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config)
+ if tc.expectErr != nil {
+ if err == nil {
+ t.Errorf("test %d, want error %v", i, tc.expectErr)
+ continue
+ }
+
+ if !reflect.DeepEqual(err, tc.expectErr) {
+ t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err)
+ }
+
+ continue
+ }
+
+ if err != nil {
+ t.Errorf("test %d, want no error, have %v", i, err)
+ continue
+ }
+
+ have, err := json.Marshal(result)
+ if err != nil {
+ t.Errorf("Error in Marshal: %v", err)
+ }
+
+ want := tc.want
+ if string(have) != want {
+ t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want)
+ }
+ }
+}
+
func TestTracingWithOverrides(t *testing.T) {
t.Parallel()
// Initialize test accounts
diff --git a/go.mod b/go.mod
index 968b96afc5..593fd031c8 100644
--- a/go.mod
+++ b/go.mod
@@ -43,6 +43,8 @@ require (
github.com/hashicorp/go-bexpr v0.1.10
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/hashicorp/hcl/v2 v2.10.1
+ github.com/heimdalr/dag v1.2.1
+ github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e
github.com/holiman/bloomfilter/v2 v2.0.3
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c
github.com/huin/goupnp v1.0.3
@@ -180,6 +182,10 @@ require (
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 // indirect
github.com/cosmos/ledger-cosmos-go v0.10.3 // indirect
github.com/cosmos/ledger-go v0.9.2 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+ github.com/deepmap/oapi-codegen v1.8.2 // indirect
+ github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
+ github.com/emirpasic/gods v1.18.1
github.com/etcd-io/bbolt v1.3.3 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
diff --git a/graphql/graphql.go b/graphql/graphql.go
index 45a1f7a2aa..f2cba8f381 100644
--- a/graphql/graphql.go
+++ b/graphql/graphql.go
@@ -1067,7 +1067,8 @@ func (c *CallResult) Status() Long {
func (b *Block) Call(ctx context.Context, args struct {
Data ethapi.TransactionArgs
}) (*CallResult, error) {
- result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
+ // TODO marcello check args number
+ result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
if err != nil {
return nil, err
}
@@ -1131,7 +1132,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.TransactionArgs
}) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
- result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap())
+ result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap())
if err != nil {
return nil, err
}
diff --git a/internal/cli/server/chains/mumbai.go b/internal/cli/server/chains/mumbai.go
index b66b46a86f..f9dd7fd989 100644
--- a/internal/cli/server/chains/mumbai.go
+++ b/internal/cli/server/chains/mumbai.go
@@ -28,8 +28,13 @@ var mumbaiTestnet = &Chain{
BerlinBlock: big.NewInt(13996000),
LondonBlock: big.NewInt(22640000),
Bor: ¶ms.BorConfig{
- JaipurBlock: big.NewInt(22770000),
- DelhiBlock: big.NewInt(29638656),
+ JaipurBlock: big.NewInt(22770000),
+ DelhiBlock: big.NewInt(29638656),
+ ParallelUniverseBlock: big.NewInt(0),
+ IndoreBlock: big.NewInt(36877056),
+ StateSyncConfirmationDelay: map[string]uint64{
+ "36877056": 128,
+ },
Period: map[string]uint64{
"0": 2,
"25275000": 5,
diff --git a/internal/cli/server/chains/test_files/chain_legacy_test.json b/internal/cli/server/chains/test_files/chain_legacy_test.json
index b97c8b1f8e..b67dfd30d5 100644
--- a/internal/cli/server/chains/test_files/chain_legacy_test.json
+++ b/internal/cli/server/chains/test_files/chain_legacy_test.json
@@ -48,7 +48,12 @@
"22640000": "0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
},
"jaipurBlock": 22770000,
- "delhiBlock": 29638656
+ "delhiBlock": 29638656,
+ "parallelUniverseBlock": 0,
+ "indoreBlock": 36877056,
+ "stateSyncConfirmationDelay": {
+ "36877056": 128
+ }
}
},
"nonce": "0x0",
diff --git a/internal/cli/server/chains/test_files/chain_test.json b/internal/cli/server/chains/test_files/chain_test.json
index 7907adfcfa..5b1f9ada40 100644
--- a/internal/cli/server/chains/test_files/chain_test.json
+++ b/internal/cli/server/chains/test_files/chain_test.json
@@ -50,7 +50,12 @@
"22640000":"0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
},
"jaipurBlock":22770000,
- "delhiBlock": 29638656
+ "delhiBlock": 29638656,
+ "parallelUniverseBlock": 0,
+ "indoreBlock": 36877056,
+ "stateSyncConfirmationDelay": {
+ "36877056": 128
+ }
}
},
"nonce":"0x0",
diff --git a/internal/cli/server/config.go b/internal/cli/server/config.go
index 50a3a41743..4e8553ec62 100644
--- a/internal/cli/server/config.go
+++ b/internal/cli/server/config.go
@@ -127,6 +127,9 @@ type Config struct {
// Developer has the developer mode related settings
Developer *DeveloperConfig `hcl:"developer,block" toml:"developer,block"`
+ // ParallelEVM has the parallel evm related settings
+ ParallelEVM *ParallelEVMConfig `hcl:"parallelevm,block" toml:"parallelevm,block"`
+
// Develop Fake Author mode to produce blocks without authorisation
DevFakeAuthor bool `hcl:"devfakeauthor,optional" toml:"devfakeauthor,optional"`
@@ -309,9 +312,10 @@ type SealerConfig struct {
GasPriceRaw string `hcl:"gasprice,optional" toml:"gasprice,optional"`
// The time interval for miner to re-create mining work.
- Recommit time.Duration `hcl:"-,optional" toml:"-"`
- RecommitRaw string `hcl:"recommit,optional" toml:"recommit,optional"`
- CommitInterruptFlag bool `hcl:"commitinterrupt,optional" toml:"commitinterrupt,optional"`
+ Recommit time.Duration `hcl:"-,optional" toml:"-"`
+ RecommitRaw string `hcl:"recommit,optional" toml:"recommit,optional"`
+
+ CommitInterruptFlag bool `hcl:"commitinterrupt,optional" toml:"commitinterrupt,optional"`
}
type JsonRPCConfig struct {
@@ -569,6 +573,12 @@ type DeveloperConfig struct {
GasLimit uint64 `hcl:"gaslimit,optional" toml:"gaslimit,optional"`
}
+type ParallelEVMConfig struct {
+ Enable bool `hcl:"enable,optional" toml:"enable,optional"`
+
+ SpeculativeProcesses int `hcl:"procs,optional" toml:"procs,optional"`
+}
+
func DefaultConfig() *Config {
return &Config{
Chain: "mainnet",
@@ -695,7 +705,7 @@ func DefaultConfig() *Config {
Enabled: false,
Expensive: false,
PrometheusAddr: "127.0.0.1:7071",
- OpenCollectorEndpoint: "127.0.0.1:4317",
+ OpenCollectorEndpoint: "",
InfluxDB: &InfluxDBConfig{
V1Enabled: false,
Endpoint: "",
@@ -748,6 +758,10 @@ func DefaultConfig() *Config {
BlockProfileRate: 0,
// CPUProfile: "",
},
+ ParallelEVM: &ParallelEVMConfig{
+ Enable: true,
+ SpeculativeProcesses: 8,
+ },
}
}
@@ -1131,6 +1145,8 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (*
n.BorLogs = c.BorLogs
n.DatabaseHandles = dbHandles
+ n.ParallelEVM.Enable = c.ParallelEVM.Enable
+ n.ParallelEVM.SpeculativeProcesses = c.ParallelEVM.SpeculativeProcesses
n.RPCReturnDataLimit = c.RPCReturnDataLimit
if c.Ancient != "" {
diff --git a/internal/cli/server/flags.go b/internal/cli/server/flags.go
index 0c38da3823..470a377012 100644
--- a/internal/cli/server/flags.go
+++ b/internal/cli/server/flags.go
@@ -904,6 +904,20 @@ func (c *Command) Flags() *flagset.Flagset {
Value: &c.cliConfig.Developer.Period,
Default: c.cliConfig.Developer.Period,
})
+
+ // parallelevm
+ f.BoolFlag(&flagset.BoolFlag{
+ Name: "parallelevm.enable",
+ Usage: "Enable Block STM",
+ Value: &c.cliConfig.ParallelEVM.Enable,
+ Default: c.cliConfig.ParallelEVM.Enable,
+ })
+ f.IntFlag(&flagset.IntFlag{
+ Name: "parallelevm.procs",
+ Usage: "Number of speculative processes (cores) in Block STM",
+ Value: &c.cliConfig.ParallelEVM.SpeculativeProcesses,
+ Default: c.cliConfig.ParallelEVM.SpeculativeProcesses,
+ })
f.Uint64Flag(&flagset.Uint64Flag{
Name: "dev.gaslimit",
Usage: "Initial block gas limit",
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index b60ed933fd..265ed53bb2 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -1053,16 +1053,40 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
}
}
-func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
+func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
- state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
- if state == nil || err != nil {
- return nil, err
+ var (
+ header *types.Header
+ err error
+ )
+
+ // Fetch the state and header from blockNumberOrHash if it's coming from normal eth_call path.
+ if state == nil {
+ state, header, err = b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+ if state == nil || err != nil {
+ return nil, err
+ }
+ } else {
+ // Fetch the header from the given blockNumberOrHash. Note that this path is only taken
+ // when we're doing a call from bor consensus to fetch data from genesis contracts. It's
+ // necessary to fetch header using header hash as we might be experiencing a reorg and there
+ // can be multiple headers with same number.
+ header, err = b.HeaderByHash(ctx, *blockNrOrHash.BlockHash)
+ if header == nil || err != nil {
+ log.Warn("Error fetching header on CallWithState", "err", err)
+ return nil, err
+ }
}
+
if err := overrides.Apply(state); err != nil {
return nil, err
}
+
+ return doCallWithState(ctx, b, args, header, state, timeout, globalGasCap)
+}
+
+func doCallWithState(ctx context.Context, b Backend, args TransactionArgs, header *types.Header, state *state.StateDB, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
@@ -1146,7 +1170,20 @@ func (e *revertError) ErrorData() interface{} {
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
- result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
+ return s.CallWithState(ctx, args, blockNrOrHash, nil, overrides)
+}
+
+// CallWithState executes the given transaction on the given state for
+// the given block number. Note that as it does an EVM call, fields in
+// the underlying state will change. Make sure to handle it outside of
+// this function (ideally by sending a copy of state).
+//
+// Additionally, the caller can specify a batch of contract for fields overriding.
+//
+// Note, this function doesn't make and changes in the state/blockchain and is
+// useful to execute and retrieve values.
+func (s *BlockChainAPI) CallWithState(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride) (hexutil.Bytes, error) {
+ result, err := DoCall(ctx, s.b, args, blockNrOrHash, state, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
if err != nil {
return nil, err
}
@@ -1236,7 +1273,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas)
- result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap)
+ result, err := DoCall(ctx, b, args, blockNrOrHash, nil, nil, 0, gasCap)
if err != nil {
if errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
diff --git a/log/logger.go b/log/logger.go
index fcc1623bab..cd076b2a1e 100644
--- a/log/logger.go
+++ b/log/logger.go
@@ -18,13 +18,13 @@ const skipLevel = 2
type Lvl int
const (
- LvlDiscard Lvl = -1
- LvlCrit Lvl = iota
+ LvlCrit Lvl = iota
LvlError
LvlWarn
LvlInfo
LvlDebug
LvlTrace
+ LvlDiscard Lvl = -1
)
// AlignedString returns a 5-character string containing the name of a Lvl.
diff --git a/metrics/prometheus/collector.go b/metrics/prometheus/collector.go
index 2bd9bf22cc..20e6ae9054 100644
--- a/metrics/prometheus/collector.go
+++ b/metrics/prometheus/collector.go
@@ -30,6 +30,7 @@ var (
typeCounterTpl = "# TYPE %s counter\n"
typeSummaryTpl = "# TYPE %s summary\n"
keyValueTpl = "%s %v\n\n"
+ keyCounterTpl = "%s %v\n"
keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n"
)
@@ -65,11 +66,16 @@ func (c *collector) addGaugeFloat64(name string, m metrics.GaugeFloat64) {
func (c *collector) addHistogram(name string, m metrics.Histogram) {
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
ps := m.Percentiles(pv)
- c.writeSummaryCounter(name, m.Count())
+
+ var sum float64 = 0
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
for i := range pv {
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
+ sum += ps[i]
}
+
+ c.writeSummarySum(name, fmt.Sprintf("%f", sum))
+ c.writeSummaryCounter(name, len(ps))
c.buff.WriteRune('\n')
}
@@ -80,7 +86,7 @@ func (c *collector) addMeter(name string, m metrics.Meter) {
func (c *collector) addTimer(name string, m metrics.Timer) {
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
ps := m.Percentiles(pv)
- c.writeSummaryCounter(name, m.Count())
+ c.writeCounter(name, m.Count())
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
for i := range pv {
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
@@ -94,11 +100,19 @@ func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) {
}
ps := m.Percentiles([]float64{50, 95, 99})
val := m.Values()
- c.writeSummaryCounter(name, len(val))
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
c.writeSummaryPercentile(name, "0.50", ps[0])
c.writeSummaryPercentile(name, "0.95", ps[1])
c.writeSummaryPercentile(name, "0.99", ps[2])
+
+ var sum int64 = 0
+
+ for _, v := range val {
+ sum += v
+ }
+
+ c.writeSummarySum(name, fmt.Sprintf("%d", sum))
+ c.writeSummaryCounter(name, len(val))
c.buff.WriteRune('\n')
}
@@ -108,17 +122,27 @@ func (c *collector) writeGaugeCounter(name string, value interface{}) {
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
}
-func (c *collector) writeSummaryCounter(name string, value interface{}) {
+func (c *collector) writeCounter(name string, value interface{}) {
name = mutateKey(name + "_count")
c.buff.WriteString(fmt.Sprintf(typeCounterTpl, name))
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
}
+func (c *collector) writeSummaryCounter(name string, value interface{}) {
+ name = mutateKey(name + "_count")
+ c.buff.WriteString(fmt.Sprintf(keyCounterTpl, name, value))
+}
+
func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
name = mutateKey(name)
c.buff.WriteString(fmt.Sprintf(keyQuantileTagValueTpl, name, p, value))
}
+func (c *collector) writeSummarySum(name string, value string) {
+ name = mutateKey(name + "_sum")
+ c.buff.WriteString(fmt.Sprintf(keyCounterTpl, name, value))
+}
+
func mutateKey(key string) string {
return strings.ReplaceAll(key, "/", "_")
}
diff --git a/metrics/prometheus/collector_test.go b/metrics/prometheus/collector_test.go
index ff87c8e765..2e5075d0f1 100644
--- a/metrics/prometheus/collector_test.go
+++ b/metrics/prometheus/collector_test.go
@@ -74,9 +74,6 @@ test_gauge 23456
# TYPE test_gauge_float64 gauge
test_gauge_float64 34567.89
-# TYPE test_histogram_count counter
-test_histogram_count 0
-
# TYPE test_histogram summary
test_histogram {quantile="0.5"} 0
test_histogram {quantile="0.75"} 0
@@ -84,6 +81,8 @@ test_histogram {quantile="0.95"} 0
test_histogram {quantile="0.99"} 0
test_histogram {quantile="0.999"} 0
test_histogram {quantile="0.9999"} 0
+test_histogram_sum 0.000000
+test_histogram_count 6
# TYPE test_meter gauge
test_meter 9999999
@@ -99,15 +98,17 @@ test_timer {quantile="0.99"} 1.2e+08
test_timer {quantile="0.999"} 1.2e+08
test_timer {quantile="0.9999"} 1.2e+08
-# TYPE test_resetting_timer_count counter
-test_resetting_timer_count 6
-
# TYPE test_resetting_timer summary
test_resetting_timer {quantile="0.50"} 12000000
test_resetting_timer {quantile="0.95"} 120000000
test_resetting_timer {quantile="0.99"} 120000000
+test_resetting_timer_sum 180000000
+test_resetting_timer_count 6
`
+
+ c.addResettingTimer("test/empty_resetting_timer", emptyResettingTimer)
+
exp := c.buff.String()
if exp != expectedOutput {
t.Log("Expected Output:\n", expectedOutput)
diff --git a/miner/fake_miner.go b/miner/fake_miner.go
index 1d623aed52..b5b90a1e4b 100644
--- a/miner/fake_miner.go
+++ b/miner/fake_miner.go
@@ -224,7 +224,7 @@ var (
// Test accounts
testBankKey, _ = crypto.GenerateKey()
TestBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
- testBankFunds = big.NewInt(8000000000000000000)
+ testBankFunds = big.NewInt(9000000000000000000)
testUserKey, _ = crypto.GenerateKey()
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey)
diff --git a/miner/test_backend.go b/miner/test_backend.go
index 3654986c1f..a0867165e3 100644
--- a/miner/test_backend.go
+++ b/miner/test_backend.go
@@ -204,7 +204,7 @@ func (b *testWorkerBackend) newRandomTxWithNonce(creation bool, nonce uint64) *t
return tx
}
-// newRandomTxWithGas creates a new transactionto deploy a storage smart contract.
+// newRandomTxWithGas creates a new transaction to deploy a storage smart contract.
func (b *testWorkerBackend) newStorageCreateContractTx() (*types.Transaction, common.Address) {
var tx *types.Transaction
@@ -427,28 +427,13 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint, opcodeDelay
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
tcount := w.current.tcount
- var interruptCtx = context.Background()
- stopFn := func() {}
- defer func() {
- stopFn()
- }()
-
- if w.interruptCommitFlag {
- block := w.chain.GetBlockByHash(w.chain.CurrentBlock().Hash())
- interruptCtx, stopFn = getInterruptTimer(ctx, w.current, block)
- // nolint : staticcheck
- interruptCtx = vm.PutCache(interruptCtx, w.interruptedTxCache)
- }
-
- w.commitTransactionsWithDelay(w.current, txset, nil, interruptCtx)
+ w.commitTransactions(w.current, txset, nil, context.Background())
// Only update the snapshot if any new transactions were added
// to the pending block
if tcount != w.current.tcount {
w.updateSnapshot(w.current)
}
-
- stopFn()
} else {
// Special case, if the consensus engine is 0 period clique(dev mode),
// submit sealing work here since all empty submission will be rejected
@@ -502,7 +487,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
return
}
- // nolint : contextcheck
+ //nolint:contextcheck
var interruptCtx = context.Background()
stopFn := func() {}
@@ -813,7 +798,6 @@ mainloop:
continue
}
// Start executing the transaction
- // TODO marcello check how "Prepare" has been replaced
env.state.SetTxContext(tx.Hash(), env.tcount)
var start time.Time
diff --git a/miner/worker.go b/miner/worker.go
index 200d54938a..f5bb3b9362 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -43,6 +43,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/bor"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/blockstm"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@@ -988,6 +989,53 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
}
var coalescedLogs []*types.Log
+ var depsMVReadList [][]blockstm.ReadDescriptor
+
+ var depsMVFullWriteList [][]blockstm.WriteDescriptor
+
+ var mvReadMapList []map[blockstm.Key]blockstm.ReadDescriptor
+
+ var deps map[int]map[int]bool
+
+ chDeps := make(chan blockstm.TxDep)
+
+ var count int
+
+ var depsWg sync.WaitGroup
+
+ var EnableMVHashMap bool
+
+ if w.chainConfig.Bor.IsParallelUniverse(env.header.Number) {
+ EnableMVHashMap = true
+ } else {
+ EnableMVHashMap = false
+ }
+
+ // create and add empty mvHashMap in statedb
+ if EnableMVHashMap {
+ depsMVReadList = [][]blockstm.ReadDescriptor{}
+
+ depsMVFullWriteList = [][]blockstm.WriteDescriptor{}
+
+ mvReadMapList = []map[blockstm.Key]blockstm.ReadDescriptor{}
+
+ deps = map[int]map[int]bool{}
+
+ chDeps = make(chan blockstm.TxDep)
+
+ count = 0
+
+ depsWg.Add(1)
+
+ go func(chDeps chan blockstm.TxDep) {
+ for t := range chDeps {
+ deps = blockstm.UpdateDeps(deps, t)
+ }
+
+ depsWg.Done()
+ }(chDeps)
+ }
+
initialGasLimit := env.gasPool.Gas()
initialTxs := txs.GetTxs()
@@ -1007,6 +1055,10 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
mainloop:
for {
if interruptCtx != nil {
+ if EnableMVHashMap {
+ env.state.AddEmptyMVHashMap()
+ }
+
// case of interrupting by timeout
select {
case <-interruptCtx.Done():
@@ -1069,6 +1121,22 @@ mainloop:
// Everything ok, collect the logs and shift in the next transaction from the same account
coalescedLogs = append(coalescedLogs, logs...)
env.tcount++
+
+ if EnableMVHashMap {
+ depsMVReadList = append(depsMVReadList, env.state.MVReadList())
+ depsMVFullWriteList = append(depsMVFullWriteList, env.state.MVFullWriteList())
+ mvReadMapList = append(mvReadMapList, env.state.MVReadMap())
+
+ temp := blockstm.TxDep{
+ Index: env.tcount - 1,
+ ReadList: depsMVReadList[count],
+ FullWriteList: depsMVFullWriteList,
+ }
+
+ chDeps <- temp
+ count++
+ }
+
txs.Shift()
log.OnDebug(func(lg log.Logging) {
@@ -1081,6 +1149,50 @@ mainloop:
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
txs.Pop()
}
+
+ if EnableMVHashMap {
+ env.state.ClearReadMap()
+ env.state.ClearWriteMap()
+ }
+ }
+
+ // nolint:nestif
+ if EnableMVHashMap {
+ close(chDeps)
+ depsWg.Wait()
+
+ if len(mvReadMapList) > 0 {
+ tempDeps := make([][]uint64, len(mvReadMapList))
+
+ for j := range deps[0] {
+ tempDeps[0] = append(tempDeps[0], uint64(j))
+ }
+
+ delayFlag := true
+
+ for i := 1; i <= len(mvReadMapList)-1; i++ {
+ reads := mvReadMapList[i-1]
+
+ _, ok1 := reads[blockstm.NewSubpathKey(env.coinbase, state.BalancePath)]
+ _, ok2 := reads[blockstm.NewSubpathKey(common.HexToAddress(w.chainConfig.Bor.CalculateBurntContract(env.header.Number.Uint64())), state.BalancePath)]
+
+ if ok1 || ok2 {
+ delayFlag = false
+ }
+
+ for j := range deps[i] {
+ tempDeps[i] = append(tempDeps[i], uint64(j))
+ }
+ }
+
+ if delayFlag {
+ env.header.TxDependency = tempDeps
+ } else {
+ env.header.TxDependency = nil
+ }
+ } else {
+ env.header.TxDependency = nil
+ }
}
if !w.isRunning() && len(coalescedLogs) > 0 {
// We don't push the pendingLogsEvent while we are sealing. The reason is that
@@ -1493,7 +1605,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *atomic.Int32, noempt
return
}
- // nolint : contextcheck
+ // nolint:contextcheck
var interruptCtx = context.Background()
stopFn := func() {}
diff --git a/miner/worker_test.go b/miner/worker_test.go
index dbce0d01c1..51e7cef4c2 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -889,11 +889,6 @@ func BenchmarkBorMining(b *testing.B) {
chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, back.Genesis, nil, engine, vm.Config{}, nil, nil, nil)
defer chain.Stop()
- // Ignore empty commit here for less noise.
- w.skipSealHook = func(task *task) bool {
- return len(task.receipts) == 0
- }
-
// fulfill tx pool
const (
totalGas = testGas + params.TxGas
@@ -950,3 +945,126 @@ func BenchmarkBorMining(b *testing.B) {
}
}
}
+
+// uses core.NewParallelBlockChain to use the dependencies present in the block header
+// params.BorUnittestChainConfig contains the ParallelUniverseBlock ad big.NewInt(5), so the first 4 blocks will not have metadata.
+// nolint: gocognit
+func BenchmarkBorMiningBlockSTMMetadata(b *testing.B) {
+ chainConfig := params.BorUnittestChainConfig
+
+ ctrl := gomock.NewController(b)
+ defer ctrl.Finish()
+
+ ethAPIMock := api.NewMockCaller(ctrl)
+ ethAPIMock.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
+
+ spanner := bor.NewMockSpanner(ctrl)
+ spanner.EXPECT().GetCurrentValidatorsByHash(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*valset.Validator{
+ {
+ ID: 0,
+ Address: TestBankAddress,
+ VotingPower: 100,
+ ProposerPriority: 0,
+ },
+ }, nil).AnyTimes()
+
+ heimdallClientMock := mocks.NewMockIHeimdallClient(ctrl)
+ heimdallClientMock.EXPECT().Close().Times(1)
+
+ contractMock := bor.NewMockGenesisContract(ctrl)
+
+ db, _, _ := NewDBForFakes(b)
+
+ engine := NewFakeBor(b, db, chainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock)
+ defer engine.Close()
+
+ chainConfig.LondonBlock = big.NewInt(0)
+
+ w, back, _ := NewTestWorker(b, chainConfig, engine, db, 0, 0, 0, 0)
+ defer w.close()
+
+ // This test chain imports the mined blocks.
+ db2 := rawdb.NewMemoryDatabase()
+ back.Genesis.MustCommit(db2)
+
+ chain, _ := core.NewParallelBlockChain(db2, nil, back.chain.Config(), engine, vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
+ defer chain.Stop()
+
+ // Ignore empty commit here for less noise.
+ w.skipSealHook = func(task *task) bool {
+ return len(task.receipts) == 0
+ }
+
+ // fulfill tx pool
+ const (
+ totalGas = testGas + params.TxGas
+ totalBlocks = 10
+ )
+
+ var err error
+
+ txInBlock := int(back.Genesis.GasLimit/totalGas) + 1
+
+ // a bit risky
+ for i := 0; i < 2*totalBlocks*txInBlock; i++ {
+ err = back.txPool.AddLocal(back.newRandomTx(true))
+ if err != nil {
+ b.Fatal("while adding a local transaction", err)
+ }
+
+ err = back.txPool.AddLocal(back.newRandomTx(false))
+ if err != nil {
+ b.Fatal("while adding a remote transaction", err)
+ }
+ }
+
+ // Wait for mined blocks.
+ sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
+ defer sub.Unsubscribe()
+
+ b.ResetTimer()
+
+ prev := uint64(time.Now().Unix())
+
+ // Start mining!
+ w.start()
+
+ blockPeriod, ok := back.Genesis.Config.Bor.Period["0"]
+ if !ok {
+ blockPeriod = 1
+ }
+
+ for i := 0; i < totalBlocks; i++ {
+ select {
+ case ev := <-sub.Chan():
+ block := ev.Data.(core.NewMinedBlockEvent).Block
+
+ if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
+ b.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err)
+ }
+
+ // check for dependencies for block number > 4
+ if block.NumberU64() <= 4 {
+ if block.TxDependency() != nil {
+ b.Fatalf("dependency not nil")
+ }
+ } else {
+ deps := block.TxDependency()
+ if len(deps[0]) != 0 {
+ b.Fatalf("wrong dependency")
+ }
+ for i := 1; i < block.Transactions().Len(); i++ {
+ if deps[i][0] != uint64(i-1) || len(deps[i]) != 1 {
+ b.Fatalf("wrong dependency")
+ }
+ }
+ }
+
+ b.Log("block", block.NumberU64(), "time", block.Time()-prev, "txs", block.Transactions().Len(), "gasUsed", block.GasUsed(), "gasLimit", block.GasLimit())
+
+ prev = block.Time()
+ case <-time.After(time.Duration(blockPeriod) * time.Second):
+ b.Fatalf("timeout")
+ }
+ }
+}
diff --git a/packaging/templates/mainnet-v1/archive/config.toml b/packaging/templates/mainnet-v1/archive/config.toml
index b2b6d769bf..869dae3c58 100644
--- a/packaging/templates/mainnet-v1/archive/config.toml
+++ b/packaging/templates/mainnet-v1/archive/config.toml
@@ -66,6 +66,7 @@ gcmode = "archive"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml b/packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml
index f2482e0cc6..7413bc63f2 100644
--- a/packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml
+++ b/packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml
@@ -66,6 +66,7 @@ syncmode = "full"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/packaging/templates/mainnet-v1/sentry/validator/bor/config.toml b/packaging/templates/mainnet-v1/sentry/validator/bor/config.toml
index 08a113b08b..1b21818adc 100644
--- a/packaging/templates/mainnet-v1/sentry/validator/bor/config.toml
+++ b/packaging/templates/mainnet-v1/sentry/validator/bor/config.toml
@@ -68,6 +68,7 @@ syncmode = "full"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/packaging/templates/mainnet-v1/without-sentry/bor/config.toml b/packaging/templates/mainnet-v1/without-sentry/bor/config.toml
index 03c34dbea6..ffbaf984ca 100644
--- a/packaging/templates/mainnet-v1/without-sentry/bor/config.toml
+++ b/packaging/templates/mainnet-v1/without-sentry/bor/config.toml
@@ -68,6 +68,7 @@ syncmode = "full"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/packaging/templates/package_scripts/control b/packaging/templates/package_scripts/control
index 1e517b7c76..8e50843583 100644
--- a/packaging/templates/package_scripts/control
+++ b/packaging/templates/package_scripts/control
@@ -1,5 +1,5 @@
Source: bor
-Version: 0.3.9-beta
+Version: 0.3.9-stable
Section: develop
Priority: standard
Maintainer: Polygon
diff --git a/packaging/templates/package_scripts/control.arm64 b/packaging/templates/package_scripts/control.arm64
index a4d2998e31..64682b7b31 100644
--- a/packaging/templates/package_scripts/control.arm64
+++ b/packaging/templates/package_scripts/control.arm64
@@ -1,5 +1,5 @@
Source: bor
-Version: 0.3.9-beta
+Version: 0.3.9-stable
Section: develop
Priority: standard
Maintainer: Polygon
diff --git a/packaging/templates/package_scripts/control.profile.amd64 b/packaging/templates/package_scripts/control.profile.amd64
index a53dec12a6..66dc5f4f30 100644
--- a/packaging/templates/package_scripts/control.profile.amd64
+++ b/packaging/templates/package_scripts/control.profile.amd64
@@ -1,5 +1,5 @@
Source: bor-profile
-Version: 0.3.9-beta
+Version: 0.3.9-stable
Section: develop
Priority: standard
Maintainer: Polygon
diff --git a/packaging/templates/package_scripts/control.profile.arm64 b/packaging/templates/package_scripts/control.profile.arm64
index b279a50500..5c9a9ca472 100644
--- a/packaging/templates/package_scripts/control.profile.arm64
+++ b/packaging/templates/package_scripts/control.profile.arm64
@@ -1,5 +1,5 @@
Source: bor-profile
-Version: 0.3.9-beta
+Version: 0.3.9-stable
Section: develop
Priority: standard
Maintainer: Polygon
diff --git a/packaging/templates/package_scripts/control.validator b/packaging/templates/package_scripts/control.validator
index 8d26132a9a..dbe6ad6c63 100644
--- a/packaging/templates/package_scripts/control.validator
+++ b/packaging/templates/package_scripts/control.validator
@@ -1,5 +1,5 @@
Source: bor-profile
-Version: 0.3.9-beta
+Version: 0.3.9-stable
Section: develop
Priority: standard
Maintainer: Polygon
diff --git a/packaging/templates/package_scripts/control.validator.arm64 b/packaging/templates/package_scripts/control.validator.arm64
index 2b923088a6..90da8544e4 100644
--- a/packaging/templates/package_scripts/control.validator.arm64
+++ b/packaging/templates/package_scripts/control.validator.arm64
@@ -1,5 +1,5 @@
Source: bor-profile
-Version: 0.3.9-beta
+Version: 0.3.9-stable
Section: develop
Priority: standard
Maintainer: Polygon
diff --git a/packaging/templates/testnet-v4/archive/config.toml b/packaging/templates/testnet-v4/archive/config.toml
index 5831169eff..6b372e9b60 100644
--- a/packaging/templates/testnet-v4/archive/config.toml
+++ b/packaging/templates/testnet-v4/archive/config.toml
@@ -66,6 +66,7 @@ gcmode = "archive"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/packaging/templates/testnet-v4/sentry/sentry/bor/config.toml b/packaging/templates/testnet-v4/sentry/sentry/bor/config.toml
index c4ca1fa915..cf81d9a9d6 100644
--- a/packaging/templates/testnet-v4/sentry/sentry/bor/config.toml
+++ b/packaging/templates/testnet-v4/sentry/sentry/bor/config.toml
@@ -66,6 +66,7 @@ syncmode = "full"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/packaging/templates/testnet-v4/sentry/validator/bor/config.toml b/packaging/templates/testnet-v4/sentry/validator/bor/config.toml
index b39e0827a5..57e3ed286c 100644
--- a/packaging/templates/testnet-v4/sentry/validator/bor/config.toml
+++ b/packaging/templates/testnet-v4/sentry/validator/bor/config.toml
@@ -68,6 +68,7 @@ syncmode = "full"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/packaging/templates/testnet-v4/without-sentry/bor/config.toml b/packaging/templates/testnet-v4/without-sentry/bor/config.toml
index eb34a264aa..1f0dabff5d 100644
--- a/packaging/templates/testnet-v4/without-sentry/bor/config.toml
+++ b/packaging/templates/testnet-v4/without-sentry/bor/config.toml
@@ -68,6 +68,7 @@ syncmode = "full"
# etherbase = ""
# extradata = ""
# recommit = "2m5s"
+ # commitinterrupt = true
[jsonrpc]
ipcpath = "/var/lib/bor/bor.ipc"
diff --git a/params/config.go b/params/config.go
index f432ec6bbb..6ea1a6c41d 100644
--- a/params/config.go
+++ b/params/config.go
@@ -279,6 +279,7 @@ var (
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
Bor: &BorConfig{
+ ParallelUniverseBlock: big.NewInt(5),
Period: map[string]uint64{
"0": 1,
},
@@ -316,8 +317,13 @@ var (
BerlinBlock: big.NewInt(13996000),
LondonBlock: big.NewInt(22640000),
Bor: &BorConfig{
- JaipurBlock: big.NewInt(22770000),
- DelhiBlock: big.NewInt(29638656),
+ JaipurBlock: big.NewInt(22770000),
+ DelhiBlock: big.NewInt(29638656),
+ ParallelUniverseBlock: big.NewInt(0),
+ IndoreBlock: big.NewInt(36877056),
+ StateSyncConfirmationDelay: map[string]uint64{
+ "36877056": 128,
+ },
Period: map[string]uint64{
"0": 2,
"25275000": 5,
@@ -369,8 +375,9 @@ var (
BerlinBlock: big.NewInt(14750000),
LondonBlock: big.NewInt(23850000),
Bor: &BorConfig{
- JaipurBlock: big.NewInt(23850000),
- DelhiBlock: big.NewInt(38189056),
+ JaipurBlock: big.NewInt(23850000),
+ DelhiBlock: big.NewInt(38189056),
+ ParallelUniverseBlock: big.NewInt(0),
Period: map[string]uint64{
"0": 2,
},
@@ -659,17 +666,20 @@ func (c *CliqueConfig) String() string {
// BorConfig is the consensus engine configs for Matic bor based sealing.
type BorConfig struct {
- Period map[string]uint64 `json:"period"` // Number of seconds between blocks to enforce
- ProducerDelay map[string]uint64 `json:"producerDelay"` // Number of seconds delay between two producer interval
- Sprint map[string]uint64 `json:"sprint"` // Epoch length to proposer
- BackupMultiplier map[string]uint64 `json:"backupMultiplier"` // Backup multiplier to determine the wiggle time
- ValidatorContract string `json:"validatorContract"` // Validator set contract
- StateReceiverContract string `json:"stateReceiverContract"` // State receiver contract
- OverrideStateSyncRecords map[string]int `json:"overrideStateSyncRecords"` // override state records count
- BlockAlloc map[string]interface{} `json:"blockAlloc"`
- BurntContract map[string]string `json:"burntContract"` // governance contract where the token will be sent to and burnt in london fork
- JaipurBlock *big.Int `json:"jaipurBlock"` // Jaipur switch block (nil = no fork, 0 = already on jaipur)
- DelhiBlock *big.Int `json:"delhiBlock"` // Delhi switch block (nil = no fork, 0 = already on delhi)
+ Period map[string]uint64 `json:"period"` // Number of seconds between blocks to enforce
+ ProducerDelay map[string]uint64 `json:"producerDelay"` // Number of seconds delay between two producer interval
+ Sprint map[string]uint64 `json:"sprint"` // Epoch length to proposer
+ BackupMultiplier map[string]uint64 `json:"backupMultiplier"` // Backup multiplier to determine the wiggle time
+ ValidatorContract string `json:"validatorContract"` // Validator set contract
+ StateReceiverContract string `json:"stateReceiverContract"` // State receiver contract
+ OverrideStateSyncRecords map[string]int `json:"overrideStateSyncRecords"` // override state records count
+ BlockAlloc map[string]interface{} `json:"blockAlloc"`
+ BurntContract map[string]string `json:"burntContract"` // governance contract where the token will be sent to and burnt in london fork
+ JaipurBlock *big.Int `json:"jaipurBlock"` // Jaipur switch block (nil = no fork, 0 = already on jaipur)
+ DelhiBlock *big.Int `json:"delhiBlock"` // Delhi switch block (nil = no fork, 0 = already on delhi)
+ ParallelUniverseBlock *big.Int `json:"parallelUniverseBlock"` // TODO: update all occurrence, change name and finalize number (hardfork for block-stm related changes)
+ IndoreBlock *big.Int `json:"indoreBlock"` // Indore switch block (nil = no fork, 0 = already on indore)
+ StateSyncConfirmationDelay map[string]uint64 `json:"stateSyncConfirmationDelay"` // StateSync Confirmation Delay, in seconds, to calculate `to`
}
// String implements the stringer interface, returning the consensus engine details.
@@ -678,11 +688,11 @@ func (b *BorConfig) String() string {
}
func (c *BorConfig) CalculateProducerDelay(number uint64) uint64 {
- return c.calculateSprintSizeHelper(c.ProducerDelay, number)
+ return borKeyValueConfigHelper(c.ProducerDelay, number)
}
func (c *BorConfig) CalculateSprint(number uint64) uint64 {
- return c.calculateSprintSizeHelper(c.Sprint, number)
+ return borKeyValueConfigHelper(c.Sprint, number)
}
func (c *BorConfig) CalculateBackupMultiplier(number uint64) uint64 {
@@ -701,6 +711,23 @@ func (c *BorConfig) IsDelhi(number *big.Int) bool {
return isBlockForked(c.DelhiBlock, number)
}
+func (c *BorConfig) IsIndore(number *big.Int) bool {
+ return isForked(c.IndoreBlock, number)
+}
+
+func (c *BorConfig) CalculateStateSyncDelay(number uint64) uint64 {
+ return borKeyValueConfigHelper(c.StateSyncConfirmationDelay, number)
+}
+
+// TODO: modify this function once the block number is finalized
+func (c *BorConfig) IsParallelUniverse(number *big.Int) bool {
+ if c.ParallelUniverseBlock == big.NewInt(0) {
+ return false
+ }
+
+ return isForked(c.ParallelUniverseBlock, number)
+}
+
func (c *BorConfig) IsSprintStart(number uint64) bool {
return number%c.CalculateSprint(number) == 0
}
@@ -725,7 +752,7 @@ func (c *BorConfig) calculateBorConfigHelper(field map[string]uint64, number uin
return field[keys[len(keys)-1]]
}
-func (c *BorConfig) calculateSprintSizeHelper(field map[string]uint64, number uint64) uint64 {
+func borKeyValueConfigHelper(field map[string]uint64, number uint64) uint64 {
keys := make([]string, 0, len(field))
for k := range field {
keys = append(keys, k)
diff --git a/params/version.go b/params/version.go
index beccb09af5..7209254a62 100644
--- a/params/version.go
+++ b/params/version.go
@@ -21,10 +21,10 @@ import (
)
const (
- VersionMajor = 0 // Major version component of the current release
- VersionMinor = 3 // Minor version component of the current release
- VersionPatch = 9 // Patch version component of the current release
- VersionMeta = "beta" // Version metadata to append to the version string
+ VersionMajor = 0 // Major version component of the current release
+ VersionMinor = 3 // Minor version component of the current release
+ VersionPatch = 9 // Patch version component of the current release
+ VersionMeta = "stable" // Version metadata to append to the version string
)
var GitCommit string
@@ -43,7 +43,7 @@ var VersionWithMeta = func() string {
return v
}()
-// VersionWithMetaCommitDetails holds the textual version string including the metadata and Git Details.
+// VersionWithCommitDetails holds the textual version string including the metadata and Git Details.
var VersionWithMetaCommitDetails = func() string {
v := Version
if VersionMeta != "" {
diff --git a/scripts/updateVersion.sh b/scripts/updateVersion.sh
new file mode 100755
index 0000000000..ab8f3aedfa
--- /dev/null
+++ b/scripts/updateVersion.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+set -e
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+
+echo "The version is of form - VersionMajor.VersionMinor.VersionPatch-VersionMeta"
+echo "Let's take 0.3.4-beta as an example. Here:"
+echo "* VersionMajor is - 0"
+echo "* VersionMinor is - 3"
+echo "* VersionPatch is - 4"
+echo "* VersionMeta is - beta"
+echo ""
+echo "Now, enter the new version step-by-step below:"
+
+version=""
+
+# VersionMajor
+read -p "* VersionMajor: " VersionMajor
+if [ -z "$VersionMajor" ]
+then
+ echo "VersionMajor cannot be NULL"
+ exit -1
+fi
+version+=$VersionMajor
+
+# VersionMinor
+read -p "* VersionMinor: " VersionMinor
+if [ -z "$VersionMinor" ]
+then
+ echo "VersionMinor cannot be NULL"
+ exit -1
+fi
+version+="."$VersionMinor
+
+# VersionPatch
+read -p "* VersionPatch: " VersionPatch
+if [ -z "$VersionPatch" ]
+then
+ echo "VersionPatch cannot be NULL"
+ exit -1
+fi
+version+="."$VersionPatch
+
+# VersionMeta (optional)
+read -p "* VersionMeta (optional, press enter if not needed): " VersionMeta
+if [[ ! -z "$VersionMeta" ]]
+then
+ version+="-"$VersionMeta
+fi
+
+echo ""
+echo "New version is: $version"
+
+# update version in all the 6 templates
+replace="Version: "$version
+fileArray=(
+ "${DIR}/../packaging/templates/package_scripts/control"
+ "${DIR}/../packaging/templates/package_scripts/control.arm64"
+ "${DIR}/../packaging/templates/package_scripts/control.profile.amd64"
+ "${DIR}/../packaging/templates/package_scripts/control.profile.arm64"
+ "${DIR}/../packaging/templates/package_scripts/control.validator"
+ "${DIR}/../packaging/templates/package_scripts/control.validator.arm64"
+)
+for file in ${fileArray[@]}; do
+ # get the line starting with `Version` in the control file and store it in the $temp variable
+ temp=$(grep "^Version.*" $file)
+ sed -i '' "s%$temp%$replace%" $file
+done
+
+# update version in ../params/version.go
+versionFile="${DIR}/../params/version.go"
+sed -i '' "s% = .*// Major% = $VersionMajor // Major%g" $versionFile
+sed -i '' "s% = .*// Minor% = $VersionMinor // Minor%g" $versionFile
+sed -i '' "s% = .*// Patch% = $VersionPatch // Patch%g" $versionFile
+sed -i '' "s% = .*// Version metadata% = \"$VersionMeta\" // Version metadata%g" $versionFile
+gofmt -w $versionFile
+
+echo ""
+echo "Updating Version Done"
+
+exit 0
diff --git a/tests/bor/bor_test.go b/tests/bor/bor_test.go
index 5469bbccd0..24c579caad 100644
--- a/tests/bor/bor_test.go
+++ b/tests/bor/bor_test.go
@@ -544,7 +544,7 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
insertNewBlock(t, chain, block)
}
- lastStateID, _ := _bor.GenesisContractsClient.LastStateId(sprintSize)
+ lastStateID, _ := _bor.GenesisContractsClient.LastStateId(nil, sprintSize, block.Hash())
// state 6 was not written
require.Equal(t, uint64(4), lastStateID.Uint64())
@@ -573,7 +573,7 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
insertNewBlock(t, chain, block)
}
- lastStateID, _ = _bor.GenesisContractsClient.LastStateId(spanSize)
+ lastStateID, _ = _bor.GenesisContractsClient.LastStateId(nil, spanSize, block.Hash())
require.Equal(t, uint64(6), lastStateID.Uint64())
}