mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
Merge pull request #904 from maticnetwork/v0.4.0-beta-candidate
Merge develop (v0.4.0-beta-candidate) to master for v0.4.0
This commit is contained in:
commit
16746a73fa
120 changed files with 6916 additions and 817 deletions
9
.github/matic-cli-config.yml
vendored
9
.github/matic-cli-config.yml
vendored
|
|
@ -10,8 +10,12 @@ blockNumber:
|
||||||
- '0'
|
- '0'
|
||||||
blockTime:
|
blockTime:
|
||||||
- '2'
|
- '2'
|
||||||
numOfValidators: 3
|
numOfBorValidators: 3
|
||||||
numOfNonValidators: 0
|
numOfBorSentries: 0
|
||||||
|
numOfBorArchiveNodes: 0
|
||||||
|
numOfErigonValidators: 0
|
||||||
|
numOfErigonSentries: 0
|
||||||
|
numOfErigonArchiveNodes: 0
|
||||||
ethURL: http://ganache:9545
|
ethURL: http://ganache:9545
|
||||||
ethHostUser: ubuntu
|
ethHostUser: ubuntu
|
||||||
devnetType: docker
|
devnetType: docker
|
||||||
|
|
@ -19,4 +23,3 @@ borDockerBuildContext: "../../bor"
|
||||||
heimdallDockerBuildContext: "https://github.com/maticnetwork/heimdall.git#develop"
|
heimdallDockerBuildContext: "https://github.com/maticnetwork/heimdall.git#develop"
|
||||||
sprintSizeBlockNumber:
|
sprintSizeBlockNumber:
|
||||||
- '0'
|
- '0'
|
||||||
numOfArchiveNodes: 0
|
|
||||||
|
|
|
||||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -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 at least 2 reviewer or the whole pos-v1 team
|
||||||
- [ ] I have added sufficient documentation in code
|
- [ ] 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
|
- [ ] 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
|
# Cross repository changes
|
||||||
|
|
||||||
|
|
|
||||||
220
.github/workflows/packager.yml
vendored
220
.github/workflows/packager.yml
vendored
|
|
@ -61,6 +61,116 @@ jobs:
|
||||||
- name: Copying systemd file
|
- name: Copying systemd file
|
||||||
run: cp -rp packaging/templates/systemd/bor.service packaging/deb/bor/lib/systemd/system/bor.service
|
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: 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: 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: 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: amd64
|
||||||
|
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: amd64
|
||||||
|
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: amd64
|
||||||
|
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: amd64
|
||||||
|
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: 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 }}
|
- 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 }}
|
run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
|
||||||
env:
|
env:
|
||||||
|
|
@ -416,6 +526,116 @@ jobs:
|
||||||
- name: Updating the control file to use with the arm64 profile
|
- 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
|
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 }}
|
- 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 }}
|
run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -17,7 +17,7 @@ GIT_COMMIT ?= $(shell git rev-list -1 HEAD)
|
||||||
|
|
||||||
PACKAGE = github.com/ethereum/go-ethereum
|
PACKAGE = github.com/ethereum/go-ethereum
|
||||||
GO_FLAGS += -buildvcs=false
|
GO_FLAGS += -buildvcs=false
|
||||||
GO_LDFLAGS += -ldflags "-X ${PACKAGE}/params.GitCommit=${GIT_COMMIT} "
|
GO_LDFLAGS += -ldflags "-X ${PACKAGE}/params.GitCommit=${GIT_COMMIT}"
|
||||||
|
|
||||||
TESTALL = $$(go list ./... | grep -v go-ethereum/cmd/)
|
TESTALL = $$(go list ./... | grep -v go-ethereum/cmd/)
|
||||||
TESTE2E = ./tests/...
|
TESTE2E = ./tests/...
|
||||||
|
|
|
||||||
|
|
@ -637,8 +637,8 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
|
||||||
// about the transaction and calling mechanisms.
|
// about the transaction and calling mechanisms.
|
||||||
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
|
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
|
||||||
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
|
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
|
// nolint : contextcheck
|
||||||
return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb()
|
return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb(context.Background())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTransaction updates the pending block to include the given transaction.
|
// SendTransaction updates the pending block to include the given transaction.
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ syncmode = "full"
|
||||||
# etherbase = "VALIDATOR ADDRESS"
|
# etherbase = "VALIDATOR ADDRESS"
|
||||||
# extradata = ""
|
# extradata = ""
|
||||||
# recommit = "2m5s"
|
# recommit = "2m5s"
|
||||||
|
# commitinterrupt = true
|
||||||
|
|
||||||
|
|
||||||
# [jsonrpc]
|
# [jsonrpc]
|
||||||
|
|
@ -128,7 +129,7 @@ syncmode = "full"
|
||||||
metrics = true
|
metrics = true
|
||||||
# expensive = false
|
# expensive = false
|
||||||
# prometheus-addr = "127.0.0.1:7071"
|
# prometheus-addr = "127.0.0.1:7071"
|
||||||
# opencollector-endpoint = "127.0.0.1:4317"
|
# opencollector-endpoint = ""
|
||||||
# [telemetry.influx]
|
# [telemetry.influx]
|
||||||
# influxdb = false
|
# influxdb = false
|
||||||
# endpoint = ""
|
# endpoint = ""
|
||||||
|
|
@ -171,6 +172,10 @@ syncmode = "full"
|
||||||
# period = 0
|
# period = 0
|
||||||
# gaslimit = 11500000
|
# gaslimit = 11500000
|
||||||
|
|
||||||
|
# [parallelevm]
|
||||||
|
# enable = true
|
||||||
|
# procs = 8
|
||||||
|
|
||||||
# [pprof]
|
# [pprof]
|
||||||
# pprof = false
|
# pprof = false
|
||||||
# port = 6060
|
# port = 6060
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@
|
||||||
"bor": {
|
"bor": {
|
||||||
"jaipurBlock": 23850000,
|
"jaipurBlock": 23850000,
|
||||||
"delhiBlock": 38189056,
|
"delhiBlock": 38189056,
|
||||||
|
"parallelUniverseBlock": 0,
|
||||||
|
"indoreBlock": 44934656,
|
||||||
|
"stateSyncConfirmationDelay": {
|
||||||
|
"44934656": 128
|
||||||
|
},
|
||||||
"period": {
|
"period": {
|
||||||
"0": 2
|
"0": 2
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@
|
||||||
"bor": {
|
"bor": {
|
||||||
"jaipurBlock": 22770000,
|
"jaipurBlock": 22770000,
|
||||||
"delhiBlock": 29638656,
|
"delhiBlock": 29638656,
|
||||||
|
"parallelUniverseBlock": 0,
|
||||||
|
"indoreBlock": 37075456,
|
||||||
|
"stateSyncConfirmationDelay": {
|
||||||
|
"37075456": 128
|
||||||
|
},
|
||||||
"period": {
|
"period": {
|
||||||
"0": 2,
|
"0": 2,
|
||||||
"25275000": 5,
|
"25275000": 5,
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
|
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
|
||||||
|
|
||||||
// (ret []byte, usedGas uint64, failed bool, err error)
|
// (ret []byte, usedGas uint64, failed bool, err error)
|
||||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
msgResult, err := core.ApplyMessage(evm, msg, gaspool, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statedb.RevertToSnapshot(snapshot)
|
statedb.RevertToSnapshot(snapshot)
|
||||||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err)
|
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err)
|
||||||
|
|
|
||||||
|
|
@ -754,6 +754,13 @@ var (
|
||||||
Usage: "Gas price below which gpo will ignore transactions",
|
Usage: "Gas price below which gpo will ignore transactions",
|
||||||
Value: ethconfig.Defaults.GPO.IgnorePrice.Int64(),
|
Value: ethconfig.Defaults.GPO.IgnorePrice.Int64(),
|
||||||
}
|
}
|
||||||
|
// flag to set the transaction fetcher's txArrivalWait value, which is the maximum waiting
|
||||||
|
// period the fetcher will wait to receive an announced tx before explicitly requesting it
|
||||||
|
TxArrivalWaitFlag = cli.DurationFlag{
|
||||||
|
Name: "txarrivalwait",
|
||||||
|
Usage: "Maximum duration to wait for a transaction before requesting it (defaults to 500ms)",
|
||||||
|
Value: node.DefaultConfig.P2P.TxArrivalWait,
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics flags
|
// Metrics flags
|
||||||
MetricsEnabledFlag = cli.BoolFlag{
|
MetricsEnabledFlag = cli.BoolFlag{
|
||||||
|
|
@ -1288,6 +1295,10 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
cfg.NoDiscovery = true
|
cfg.NoDiscovery = true
|
||||||
cfg.DiscoveryV5 = false
|
cfg.DiscoveryV5 = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.GlobalIsSet(TxArrivalWaitFlag.Name) {
|
||||||
|
cfg.TxArrivalWait = TxArrivalWaitFlag.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNodeConfig applies node-related command line flags to the config.
|
// SetNodeConfig applies node-related command line flags to the config.
|
||||||
|
|
|
||||||
|
|
@ -75,13 +75,34 @@ func rankMapDifficulties(values map[common.Address]uint64) []difficultiesKV {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSnapshotProposerSequence retrieves the in-turn signers of all sprints in a span
|
// GetSnapshotProposerSequence retrieves the in-turn signers of all sprints in a span
|
||||||
func (api *API) GetSnapshotProposerSequence(number *rpc.BlockNumber) (BlockSigners, error) {
|
func (api *API) GetSnapshotProposerSequence(blockNrOrHash *rpc.BlockNumberOrHash) (BlockSigners, error) {
|
||||||
snapNumber := *number - 1
|
var header *types.Header
|
||||||
|
//nolint:nestif
|
||||||
|
if blockNrOrHash == nil {
|
||||||
|
header = api.chain.CurrentHeader()
|
||||||
|
} else {
|
||||||
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
||||||
|
if blockNr == rpc.LatestBlockNumber {
|
||||||
|
header = api.chain.CurrentHeader()
|
||||||
|
} else {
|
||||||
|
header = api.chain.GetHeaderByNumber(uint64(blockNr))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if blockHash, ok := blockNrOrHash.Hash(); ok {
|
||||||
|
header = api.chain.GetHeaderByHash(blockHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if header == nil {
|
||||||
|
return BlockSigners{}, errUnknownBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
snapNumber := rpc.BlockNumber(header.Number.Int64() - 1)
|
||||||
|
snap, err := api.GetSnapshot(&snapNumber)
|
||||||
|
|
||||||
var difficulties = make(map[common.Address]uint64)
|
var difficulties = make(map[common.Address]uint64)
|
||||||
|
|
||||||
snap, err := api.GetSnapshot(&snapNumber)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BlockSigners{}, err
|
return BlockSigners{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +122,7 @@ func (api *API) GetSnapshotProposerSequence(number *rpc.BlockNumber) (BlockSigne
|
||||||
|
|
||||||
rankedDifficulties := rankMapDifficulties(difficulties)
|
rankedDifficulties := rankMapDifficulties(difficulties)
|
||||||
|
|
||||||
author, err := api.GetAuthor(number)
|
author, err := api.GetAuthor(blockNrOrHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BlockSigners{}, err
|
return BlockSigners{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -117,9 +138,31 @@ func (api *API) GetSnapshotProposerSequence(number *rpc.BlockNumber) (BlockSigne
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSnapshotProposer retrieves the in-turn signer at a given block.
|
// GetSnapshotProposer retrieves the in-turn signer at a given block.
|
||||||
func (api *API) GetSnapshotProposer(number *rpc.BlockNumber) (common.Address, error) {
|
func (api *API) GetSnapshotProposer(blockNrOrHash *rpc.BlockNumberOrHash) (common.Address, error) {
|
||||||
*number -= 1
|
var header *types.Header
|
||||||
snap, err := api.GetSnapshot(number)
|
//nolint:nestif
|
||||||
|
if blockNrOrHash == nil {
|
||||||
|
header = api.chain.CurrentHeader()
|
||||||
|
} else {
|
||||||
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
||||||
|
if blockNr == rpc.LatestBlockNumber {
|
||||||
|
header = api.chain.CurrentHeader()
|
||||||
|
} else {
|
||||||
|
header = api.chain.GetHeaderByNumber(uint64(blockNr))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if blockHash, ok := blockNrOrHash.Hash(); ok {
|
||||||
|
header = api.chain.GetHeaderByHash(blockHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if header == nil {
|
||||||
|
return common.Address{}, errUnknownBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
snapNumber := rpc.BlockNumber(header.Number.Int64() - 1)
|
||||||
|
snap, err := api.GetSnapshot(&snapNumber)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
|
|
@ -129,14 +172,26 @@ func (api *API) GetSnapshotProposer(number *rpc.BlockNumber) (common.Address, er
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthor retrieves the author a block.
|
// GetAuthor retrieves the author a block.
|
||||||
func (api *API) GetAuthor(number *rpc.BlockNumber) (*common.Address, error) {
|
func (api *API) GetAuthor(blockNrOrHash *rpc.BlockNumberOrHash) (*common.Address, error) {
|
||||||
// Retrieve the requested block number (or current if none requested)
|
// Retrieve the requested block number (or current if none requested)
|
||||||
var header *types.Header
|
var header *types.Header
|
||||||
if number == nil || *number == rpc.LatestBlockNumber {
|
|
||||||
|
//nolint:nestif
|
||||||
|
if blockNrOrHash == nil {
|
||||||
header = api.chain.CurrentHeader()
|
header = api.chain.CurrentHeader()
|
||||||
} else {
|
} else {
|
||||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
||||||
|
header = api.chain.GetHeaderByNumber(uint64(blockNr))
|
||||||
|
if blockNr == rpc.LatestBlockNumber {
|
||||||
|
header = api.chain.CurrentHeader()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if blockHash, ok := blockNrOrHash.Hash(); ok {
|
||||||
|
header = api.chain.GetHeaderByHash(blockHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure we have an actually valid block and return its snapshot
|
// Ensure we have an actually valid block and return its snapshot
|
||||||
if header == nil {
|
if header == nil {
|
||||||
return nil, errUnknownBlock
|
return nil, errUnknownBlock
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"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/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
@ -11,4 +12,5 @@ import (
|
||||||
//go:generate mockgen -destination=./caller_mock.go -package=api . Caller
|
//go:generate mockgen -destination=./caller_mock.go -package=api . Caller
|
||||||
type Caller interface {
|
type Caller interface {
|
||||||
Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride) (hexutil.Bytes, error)
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
hexutil "github.com/ethereum/go-ethereum/common/hexutil"
|
hexutil "github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
state "github.com/ethereum/go-ethereum/core/state"
|
||||||
ethapi "github.com/ethereum/go-ethereum/internal/ethapi"
|
ethapi "github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
rpc "github.com/ethereum/go-ethereum/rpc"
|
rpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
gomock "github.com/golang/mock/gomock"
|
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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockCaller)(nil).Call), arg0, arg1, arg2, arg3)
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1184,22 +1184,44 @@ func (c *Bor) CommitStates(
|
||||||
fetchStart := time.Now()
|
fetchStart := time.Now()
|
||||||
number := header.Number.Uint64()
|
number := header.Number.Uint64()
|
||||||
|
|
||||||
_lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
to := time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.CalculateSprint(number)).Time), 0)
|
stateSyncDelay := c.config.CalculateStateSyncDelay(number)
|
||||||
lastStateID := _lastStateID.Uint64()
|
to = time.Unix(int64(header.Time-stateSyncDelay), 0)
|
||||||
|
log.Debug("Post Indore", "lastStateIDBig", lastStateIDBig, "to", to, "stateSyncDelay", stateSyncDelay)
|
||||||
|
} 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)
|
||||||
|
log.Debug("Pre Indore", "lastStateIDBig", lastStateIDBig, "to", to)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastStateID := lastStateIDBig.Uint64()
|
||||||
|
from = lastStateID + 1
|
||||||
|
|
||||||
log.Info(
|
log.Info(
|
||||||
"Fetching state updates from Heimdall",
|
"Fetching state updates from Heimdall",
|
||||||
"fromID", lastStateID+1,
|
"fromID", from,
|
||||||
"to", to.Format(time.RFC3339))
|
"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 {
|
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 {
|
if c.config.OverrideStateSyncRecords != nil {
|
||||||
|
|
@ -1222,7 +1244,7 @@ func (c *Bor) CommitStates(
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil {
|
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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,8 @@ func (gc *GenesisContractsClient) CommitState(
|
||||||
return gasUsed, nil
|
return gasUsed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int, error) {
|
func (gc *GenesisContractsClient) LastStateId(state *state.StateDB, number uint64, hash common.Hash) (*big.Int, error) {
|
||||||
blockNr := rpc.BlockNumber(snapshotNumber)
|
blockNr := rpc.BlockNumber(number)
|
||||||
|
|
||||||
const method = "lastStateId"
|
const method = "lastStateId"
|
||||||
|
|
||||||
|
|
@ -116,11 +116,13 @@ func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int,
|
||||||
toAddress := common.HexToAddress(gc.StateReceiverContract)
|
toAddress := common.HexToAddress(gc.StateReceiverContract)
|
||||||
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
|
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,
|
Gas: &gas,
|
||||||
To: &toAddress,
|
To: &toAddress,
|
||||||
Data: &msgData,
|
Data: &msgData,
|
||||||
}, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil)
|
}, rpc.BlockNumberOrHash{BlockNumber: &blockNr, BlockHash: &hash}, state, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package bor
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
|
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
|
||||||
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
|
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
@ -12,5 +13,5 @@ import (
|
||||||
//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract
|
//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract
|
||||||
type GenesisContract interface {
|
type GenesisContract interface {
|
||||||
CommitState(event *clerk.EventRecordWithTime, state *state.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error)
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
big "math/big"
|
big "math/big"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
|
common "github.com/ethereum/go-ethereum/common"
|
||||||
clerk "github.com/ethereum/go-ethereum/consensus/bor/clerk"
|
clerk "github.com/ethereum/go-ethereum/consensus/bor/clerk"
|
||||||
statefull "github.com/ethereum/go-ethereum/consensus/bor/statefull"
|
statefull "github.com/ethereum/go-ethereum/consensus/bor/statefull"
|
||||||
state "github.com/ethereum/go-ethereum/core/state"
|
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.
|
// 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()
|
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)
|
ret0, _ := ret[0].(*big.Int)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastStateId indicates an expected call of LastStateId.
|
// 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()
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
// 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 is a generated GoMock package.
|
||||||
package bor
|
package bor
|
||||||
|
|
@ -42,60 +42,60 @@ func (m *MockSpanner) EXPECT() *MockSpannerMockRecorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitSpan mocks base method.
|
// 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()
|
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)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitSpan indicates an expected call of CommitSpan.
|
// 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()
|
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.
|
// 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()
|
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)
|
ret0, _ := ret[0].(*span.Span)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentSpan indicates an expected call of GetCurrentSpan.
|
// 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()
|
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.
|
// 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()
|
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)
|
ret0, _ := ret[0].([]*valset.Validator)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentValidatorsByBlockNrOrHash indicates an expected call of GetCurrentValidatorsByBlockNrOrHash.
|
// 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()
|
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.
|
// 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()
|
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)
|
ret0, _ := ret[0].([]*valset.Validator)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentValidatorsByHash indicates an expected call of GetCurrentValidatorsByHash.
|
// 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()
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ func ApplyMessage(
|
||||||
msg.Data(),
|
msg.Data(),
|
||||||
msg.Gas(),
|
msg.Gas(),
|
||||||
msg.Value(),
|
msg.Value(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
// Update the state with pending changes
|
// Update the state with pending changes
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -104,6 +105,7 @@ func ApplyBorMessage(vmenv vm.EVM, msg Callmsg) (*core.ExecutionResult, error) {
|
||||||
msg.Data(),
|
msg.Data(),
|
||||||
msg.Gas(),
|
msg.Gas(),
|
||||||
msg.Value(),
|
msg.Value(),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
// Update the state with pending changes
|
// Update the state with pending changes
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/prque"
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
"github.com/ethereum/go-ethereum/common/tracing"
|
"github.com/ethereum/go-ethereum/common/tracing"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"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/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
|
@ -81,6 +82,8 @@ var (
|
||||||
blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
|
blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
|
||||||
blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil)
|
blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil)
|
||||||
blockWriteTimer = metrics.NewRegisteredTimer("chain/write", 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)
|
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
|
||||||
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
||||||
|
|
@ -220,6 +223,7 @@ type BlockChain struct {
|
||||||
validator Validator // Block and state validator interface
|
validator Validator // Block and state validator interface
|
||||||
prefetcher Prefetcher
|
prefetcher Prefetcher
|
||||||
processor Processor // Block transaction processor interface
|
processor Processor // Block transaction processor interface
|
||||||
|
parallelProcessor Processor // Parallel block transaction processor interface
|
||||||
forker *ForkChoice
|
forker *ForkChoice
|
||||||
vmConfig vm.Config
|
vmConfig vm.Config
|
||||||
|
|
||||||
|
|
@ -435,6 +439,93 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||||
return bc, nil
|
return bc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Similar to NewBlockChain, this function creates a new blockchain object, but with a parallel state processor
|
||||||
|
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.
|
// empty returns an indicator whether the blockchain is empty.
|
||||||
// Note, it's a special case that we connect a non-empty ancient
|
// 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
|
// database with an empty node, so that we can plugin the ancient
|
||||||
|
|
@ -1761,14 +1852,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||||
}
|
}
|
||||||
statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
|
|
||||||
if err != nil {
|
|
||||||
return it.index, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable prefetching to pull in trie node paths while processing transactions
|
|
||||||
statedb.StartPrefetcher("chain")
|
|
||||||
activeState = statedb
|
|
||||||
|
|
||||||
// If we have a followup block, run that against the current state to pre-cache
|
// If we have a followup block, run that against the current state to pre-cache
|
||||||
// transactions and probabilistically some of the account/storage trie nodes.
|
// transactions and probabilistically some of the account/storage trie nodes.
|
||||||
|
|
@ -1790,7 +1873,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
|
||||||
|
|
||||||
// Process block using the parent state as reference point
|
// Process block using the parent state as reference point
|
||||||
substart := time.Now()
|
substart := 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 {
|
if err != nil {
|
||||||
bc.reportBlock(block, receipts, err)
|
bc.reportBlock(block, receipts, err)
|
||||||
atomic.StoreUint32(&followupInterrupt, 1)
|
atomic.StoreUint32(&followupInterrupt, 1)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"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/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -123,9 +122,11 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara
|
||||||
if full {
|
if full {
|
||||||
cur := blockchain.CurrentBlock()
|
cur := blockchain.CurrentBlock()
|
||||||
tdPre = blockchain.GetTd(cur.Hash(), cur.NumberU64())
|
tdPre = blockchain.GetTd(cur.Hash(), cur.NumberU64())
|
||||||
|
|
||||||
if err := testBlockChainImport(blockChainB, blockchain); err != nil {
|
if err := testBlockChainImport(blockChainB, blockchain); err != nil {
|
||||||
t.Fatalf("failed to import forked block chain: %v", err)
|
t.Fatalf("failed to import forked block chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
last := blockChainB[len(blockChainB)-1]
|
last := blockChainB[len(blockChainB)-1]
|
||||||
tdPost = blockchain.GetTd(last.Hash(), last.NumberU64())
|
tdPost = blockchain.GetTd(last.Hash(), last.NumberU64())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -156,11 +157,9 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache, nil)
|
|
||||||
if err != nil {
|
receipts, _, usedGas, statedb, err := blockchain.ProcessBlock(block, blockchain.GetBlockByHash(block.ParentHash()).Header())
|
||||||
return err
|
|
||||||
}
|
|
||||||
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
blockchain.reportBlock(block, receipts, err)
|
blockchain.reportBlock(block, receipts, err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -180,6 +179,25 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
||||||
return nil
|
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()
|
||||||
|
|
||||||
|
blockChainB := makeFakeNonEmptyBlockChain(blockchain.CurrentBlock(), 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
|
// testHeaderChainImport tries to process a chain of header, writing them into
|
||||||
// the database if successful.
|
// the database if successful.
|
||||||
func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error {
|
func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error {
|
||||||
|
|
|
||||||
201
core/blockstm/dag.go
Normal file
201
core/blockstm/dag.go
Normal file
|
|
@ -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))))
|
||||||
|
}
|
||||||
641
core/blockstm/executor.go
Normal file
641
core/blockstm/executor.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
984
core/blockstm/executor_test.go
Normal file
984
core/blockstm/executor_test.go
Normal file
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
290
core/blockstm/mvhashmap.go
Normal file
290
core/blockstm/mvhashmap.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
344
core/blockstm/mvhashmap_test.go
Normal file
344
core/blockstm/mvhashmap_test.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
225
core/blockstm/status.go
Normal file
225
core/blockstm/status.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
82
core/blockstm/status_test.go
Normal file
82
core/blockstm/status_test.go
Normal file
|
|
@ -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())
|
||||||
|
}
|
||||||
106
core/blockstm/txio.go
Normal file
106
core/blockstm/txio.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -105,7 +105,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
|
||||||
b.SetCoinbase(common.Address{})
|
b.SetCoinbase(common.Address{})
|
||||||
}
|
}
|
||||||
b.statedb.Prepare(tx.Hash(), len(b.txs))
|
b.statedb.Prepare(tx.Hash(), len(b.txs))
|
||||||
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
|
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -335,6 +335,19 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd
|
||||||
return blocks
|
return 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 {
|
type fakeChainReader struct {
|
||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
stateSyncData []*types.StateSyncData
|
stateSyncData []*types.StateSyncData
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -83,7 +84,12 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash
|
||||||
// Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...]
|
// Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...]
|
||||||
var cache []common.Hash
|
var cache []common.Hash
|
||||||
|
|
||||||
|
cacheMutex := &sync.Mutex{}
|
||||||
|
|
||||||
return func(n uint64) common.Hash {
|
return func(n uint64) common.Hash {
|
||||||
|
cacheMutex.Lock()
|
||||||
|
defer cacheMutex.Unlock()
|
||||||
|
|
||||||
// If there's no hash cache yet, make one
|
// If there's no hash cache yet, make one
|
||||||
if len(cache) == 0 {
|
if len(cache) == 0 {
|
||||||
cache = append(cache, ref.ParentHash)
|
cache = append(cache, ref.ParentHash)
|
||||||
|
|
|
||||||
429
core/parallel_state_processor.go
Normal file
429
core/parallel_state_processor.go
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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
|
// journalEntry is a modification entry in the state change journal that can be
|
||||||
|
|
@ -143,6 +144,7 @@ type (
|
||||||
func (ch createObjectChange) revert(s *StateDB) {
|
func (ch createObjectChange) revert(s *StateDB) {
|
||||||
delete(s.stateObjects, *ch.account)
|
delete(s.stateObjects, *ch.account)
|
||||||
delete(s.stateObjectsDirty, *ch.account)
|
delete(s.stateObjectsDirty, *ch.account)
|
||||||
|
RevertWrite(s, blockstm.NewAddressKey(*ch.account))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) dirtied() *common.Address {
|
func (ch createObjectChange) dirtied() *common.Address {
|
||||||
|
|
@ -151,6 +153,7 @@ func (ch createObjectChange) dirtied() *common.Address {
|
||||||
|
|
||||||
func (ch resetObjectChange) revert(s *StateDB) {
|
func (ch resetObjectChange) revert(s *StateDB) {
|
||||||
s.setStateObject(ch.prev)
|
s.setStateObject(ch.prev)
|
||||||
|
RevertWrite(s, blockstm.NewAddressKey(ch.prev.address))
|
||||||
if !ch.prevdestruct && s.snap != nil {
|
if !ch.prevdestruct && s.snap != nil {
|
||||||
delete(s.snapDestructs, ch.prev.addrHash)
|
delete(s.snapDestructs, ch.prev.addrHash)
|
||||||
}
|
}
|
||||||
|
|
@ -165,6 +168,7 @@ func (ch suicideChange) revert(s *StateDB) {
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
obj.suicided = ch.prev
|
obj.suicided = ch.prev
|
||||||
obj.setBalance(ch.prevbalance)
|
obj.setBalance(ch.prevbalance)
|
||||||
|
RevertWrite(s, blockstm.NewSubpathKey(*ch.account, SuicidePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,6 +203,7 @@ func (ch nonceChange) dirtied() *common.Address {
|
||||||
|
|
||||||
func (ch codeChange) revert(s *StateDB) {
|
func (ch codeChange) revert(s *StateDB) {
|
||||||
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
|
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
|
||||||
|
RevertWrite(s, blockstm.NewSubpathKey(*ch.account, CodePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch codeChange) dirtied() *common.Address {
|
func (ch codeChange) dirtied() *common.Address {
|
||||||
|
|
@ -207,6 +212,7 @@ func (ch codeChange) dirtied() *common.Address {
|
||||||
|
|
||||||
func (ch storageChange) revert(s *StateDB) {
|
func (ch storageChange) revert(s *StateDB) {
|
||||||
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
|
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
|
||||||
|
RevertWrite(s, blockstm.NewStateKey(*ch.account, ch.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) dirtied() *common.Address {
|
func (ch storageChange) dirtied() *common.Address {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -79,6 +80,14 @@ type StateDB struct {
|
||||||
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
|
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
|
||||||
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
|
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
|
||||||
|
|
||||||
|
// 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.
|
// DB error.
|
||||||
// State objects are used by the consensus core and VM which are
|
// State objects are used by the consensus core and VM which are
|
||||||
// unable to deal with database-level errors. Any error that occurs
|
// unable to deal with database-level errors. Any error that occurs
|
||||||
|
|
@ -138,6 +147,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||||
stateObjects: make(map[common.Address]*stateObject),
|
stateObjects: make(map[common.Address]*stateObject),
|
||||||
stateObjectsPending: make(map[common.Address]struct{}),
|
stateObjectsPending: make(map[common.Address]struct{}),
|
||||||
stateObjectsDirty: make(map[common.Address]struct{}),
|
stateObjectsDirty: make(map[common.Address]struct{}),
|
||||||
|
revertedKeys: make(map[blockstm.Key]struct{}),
|
||||||
logs: make(map[common.Hash][]*types.Log),
|
logs: make(map[common.Hash][]*types.Log),
|
||||||
preimages: make(map[common.Hash][]byte),
|
preimages: make(map[common.Hash][]byte),
|
||||||
journal: newJournal(),
|
journal: newJournal(),
|
||||||
|
|
@ -154,6 +164,281 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||||
return sdb, nil
|
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 (sdb *StateDB) SetMVHashmap(mvhm *blockstm.MVHashMap) {
|
||||||
|
sdb.mvHashmap = mvhm
|
||||||
|
sdb.dep = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sdb *StateDB) GetMVHashmap() *blockstm.MVHashMap {
|
||||||
|
return sdb.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply entries in a given write set to StateDB. Note that this function does not change MVHashMap nor write set
|
||||||
|
// of the current StateDB.
|
||||||
|
func (sw *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)
|
||||||
|
sw.SetState(addr, stateKey, state)
|
||||||
|
} else if path.IsAddress() {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
addr := path.GetAddress()
|
||||||
|
switch path.GetSubpath() {
|
||||||
|
case BalancePath:
|
||||||
|
sw.SetBalance(addr, sr.GetBalance(addr))
|
||||||
|
case NoncePath:
|
||||||
|
sw.SetNonce(addr, sr.GetNonce(addr))
|
||||||
|
case CodePath:
|
||||||
|
sw.SetCode(addr, sr.GetCode(addr))
|
||||||
|
case SuicidePath:
|
||||||
|
stateObject := sr.getDeletedStateObject(addr)
|
||||||
|
if stateObject != nil && stateObject.deleted {
|
||||||
|
sw.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// get 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// get 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// add 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
|
// 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
|
// state trie concurrently while the state is mutated so that when we reach the
|
||||||
// commit phase, most of the needed data is already hot.
|
// commit phase, most of the needed data is already hot.
|
||||||
|
|
@ -257,22 +542,40 @@ func (s *StateDB) Empty(addr common.Address) bool {
|
||||||
return so == nil || so.empty()
|
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
|
// GetBalance retrieves the balance from the given address or 0 if object not found
|
||||||
func (s *StateDB) GetBalance(addr common.Address) *big.Int {
|
func (s *StateDB) GetBalance(addr common.Address) *big.Int {
|
||||||
|
return MVRead(s, blockstm.NewSubpathKey(addr, BalancePath), common.Big0, func(s *StateDB) *big.Int {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.Balance()
|
return stateObject.Balance()
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Big0
|
return common.Big0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) GetNonce(addr common.Address) uint64 {
|
func (s *StateDB) GetNonce(addr common.Address) uint64 {
|
||||||
|
return MVRead(s, blockstm.NewSubpathKey(addr, NoncePath), 0, func(s *StateDB) uint64 {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.Nonce()
|
return stateObject.Nonce()
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxIndex returns the current transaction index set by Prepare.
|
// TxIndex returns the current transaction index set by Prepare.
|
||||||
|
|
@ -280,37 +583,52 @@ func (s *StateDB) TxIndex() int {
|
||||||
return s.txIndex
|
return s.txIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) Version() blockstm.Version {
|
||||||
|
return blockstm.Version{
|
||||||
|
TxnIndex: s.txIndex,
|
||||||
|
Incarnation: s.incarnation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StateDB) GetCode(addr common.Address) []byte {
|
func (s *StateDB) GetCode(addr common.Address) []byte {
|
||||||
|
return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), nil, func(s *StateDB) []byte {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.Code(s.db)
|
return stateObject.Code(s.db)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) GetCodeSize(addr common.Address) int {
|
func (s *StateDB) GetCodeSize(addr common.Address) int {
|
||||||
|
return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), 0, func(s *StateDB) int {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.CodeSize(s.db)
|
return stateObject.CodeSize(s.db)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||||
|
return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), common.Hash{}, func(s *StateDB) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject == nil {
|
if stateObject == nil {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
return common.BytesToHash(stateObject.CodeHash())
|
return common.BytesToHash(stateObject.CodeHash())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState retrieves a value from the given account's storage trie.
|
// GetState retrieves a value from the given account's storage trie.
|
||||||
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
|
return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.GetState(s.db, hash)
|
return stateObject.GetState(s.db, hash)
|
||||||
}
|
}
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProof returns the Merkle proof for a given account.
|
// GetProof returns the Merkle proof for a given account.
|
||||||
|
|
@ -338,11 +656,13 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
|
||||||
|
|
||||||
// GetCommittedState retrieves a value from the given account's committed storage trie.
|
// GetCommittedState retrieves a value from the given account's committed storage trie.
|
||||||
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
|
return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.GetCommittedState(s.db, hash)
|
return stateObject.GetCommittedState(s.db, hash)
|
||||||
}
|
}
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database retrieves the low level database supporting the lower level trie ops.
|
// Database retrieves the low level database supporting the lower level trie ops.
|
||||||
|
|
@ -363,11 +683,13 @@ func (s *StateDB) StorageTrie(addr common.Address) Trie {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) HasSuicided(addr common.Address) bool {
|
func (s *StateDB) HasSuicided(addr common.Address) bool {
|
||||||
|
return MVRead(s, blockstm.NewSubpathKey(addr, SuicidePath), false, func(s *StateDB) bool {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.suicided
|
return stateObject.suicided
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -377,44 +699,68 @@ func (s *StateDB) HasSuicided(addr common.Address) bool {
|
||||||
// AddBalance adds amount to the account associated with addr.
|
// AddBalance adds amount to the account associated with addr.
|
||||||
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
|
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
|
||||||
stateObject := s.GetOrNewStateObject(addr)
|
stateObject := s.GetOrNewStateObject(addr)
|
||||||
|
|
||||||
|
if s.mvHashmap != nil {
|
||||||
|
// ensure a read balance operation is recorded in mvHashmap
|
||||||
|
s.GetBalance(addr)
|
||||||
|
}
|
||||||
|
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
stateObject = s.mvRecordWritten(stateObject)
|
||||||
stateObject.AddBalance(amount)
|
stateObject.AddBalance(amount)
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubBalance subtracts amount from the account associated with addr.
|
// SubBalance subtracts amount from the account associated with addr.
|
||||||
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
|
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
|
||||||
stateObject := s.GetOrNewStateObject(addr)
|
stateObject := s.GetOrNewStateObject(addr)
|
||||||
|
|
||||||
|
if s.mvHashmap != nil {
|
||||||
|
// ensure a read balance operation is recorded in mvHashmap
|
||||||
|
s.GetBalance(addr)
|
||||||
|
}
|
||||||
|
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
stateObject = s.mvRecordWritten(stateObject)
|
||||||
stateObject.SubBalance(amount)
|
stateObject.SubBalance(amount)
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) {
|
func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) {
|
||||||
stateObject := s.GetOrNewStateObject(addr)
|
stateObject := s.GetOrNewStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
stateObject = s.mvRecordWritten(stateObject)
|
||||||
stateObject.SetBalance(amount)
|
stateObject.SetBalance(amount)
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
|
func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
|
||||||
stateObject := s.GetOrNewStateObject(addr)
|
stateObject := s.GetOrNewStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
stateObject = s.mvRecordWritten(stateObject)
|
||||||
stateObject.SetNonce(nonce)
|
stateObject.SetNonce(nonce)
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, NoncePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) SetCode(addr common.Address, code []byte) {
|
func (s *StateDB) SetCode(addr common.Address, code []byte) {
|
||||||
stateObject := s.GetOrNewStateObject(addr)
|
stateObject := s.GetOrNewStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
stateObject = s.mvRecordWritten(stateObject)
|
||||||
stateObject.SetCode(crypto.Keccak256Hash(code), code)
|
stateObject.SetCode(crypto.Keccak256Hash(code), code)
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, CodePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
|
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
|
||||||
stateObject := s.GetOrNewStateObject(addr)
|
stateObject := s.GetOrNewStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
stateObject = s.mvRecordWritten(stateObject)
|
||||||
stateObject.SetState(s.db, key, value)
|
stateObject.SetState(s.db, key, value)
|
||||||
|
MVWrite(s, blockstm.NewStateKey(addr, key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -437,6 +783,8 @@ func (s *StateDB) Suicide(addr common.Address) bool {
|
||||||
if stateObject == nil {
|
if stateObject == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stateObject = s.mvRecordWritten(stateObject)
|
||||||
s.journal.append(suicideChange{
|
s.journal.append(suicideChange{
|
||||||
account: &addr,
|
account: &addr,
|
||||||
prev: stateObject.suicided,
|
prev: stateObject.suicided,
|
||||||
|
|
@ -445,6 +793,9 @@ func (s *StateDB) Suicide(addr common.Address) bool {
|
||||||
stateObject.markSuicided()
|
stateObject.markSuicided()
|
||||||
stateObject.data.Balance = new(big.Int)
|
stateObject.data.Balance = new(big.Int)
|
||||||
|
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, SuicidePath))
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,15 +852,16 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
||||||
// flag set. This is needed by the state journal to revert to the correct s-
|
// 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.
|
// destructed object instead of wiping all knowledge about the state object.
|
||||||
func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||||
|
return MVRead(s, blockstm.NewAddressKey(addr), nil, func(s *StateDB) *stateObject {
|
||||||
// Prefer live objects if any is available
|
// Prefer live objects if any is available
|
||||||
if obj := s.stateObjects[addr]; obj != nil {
|
if obj := s.stateObjects[addr]; obj != nil {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
// If no live objects are available, attempt to use snapshots
|
// If no live objects are available, attempt to use snapshots
|
||||||
var data *types.StateAccount
|
var data *types.StateAccount
|
||||||
if s.snap != nil {
|
if s.snap != nil { // nolint
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes()))
|
acc, err := s.snap.Account(crypto.HashData(crypto.NewKeccakState(), addr.Bytes()))
|
||||||
if metrics.EnabledExpensive {
|
if metrics.EnabledExpensive {
|
||||||
s.SnapshotAccountReads += time.Since(start)
|
s.SnapshotAccountReads += time.Since(start)
|
||||||
}
|
}
|
||||||
|
|
@ -555,6 +907,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||||
obj := newObject(s, addr, *data)
|
obj := newObject(s, addr, *data)
|
||||||
s.setStateObject(obj)
|
s.setStateObject(obj)
|
||||||
return obj
|
return obj
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) setStateObject(object *stateObject) {
|
func (s *StateDB) setStateObject(object *stateObject) {
|
||||||
|
|
@ -570,6 +923,28 @@ func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
|
||||||
return 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
|
// 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.
|
// the given address, it is overwritten and returned as the second return value.
|
||||||
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
|
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
|
||||||
|
|
@ -589,6 +964,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
|
||||||
s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
|
s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
|
||||||
}
|
}
|
||||||
s.setStateObject(newobj)
|
s.setStateObject(newobj)
|
||||||
|
|
||||||
|
MVWrite(s, blockstm.NewAddressKey(addr))
|
||||||
if prev != nil && !prev.deleted {
|
if prev != nil && !prev.deleted {
|
||||||
return newobj, prev
|
return newobj, prev
|
||||||
}
|
}
|
||||||
|
|
@ -609,6 +986,7 @@ func (s *StateDB) CreateAccount(addr common.Address) {
|
||||||
newObj, prev := s.createObject(addr)
|
newObj, prev := s.createObject(addr)
|
||||||
if prev != nil {
|
if prev != nil {
|
||||||
newObj.setBalance(prev.data.Balance)
|
newObj.setBalance(prev.data.Balance)
|
||||||
|
MVWrite(s, blockstm.NewSubpathKey(addr, BalancePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -651,6 +1029,7 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
|
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
|
||||||
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
|
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
|
||||||
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
|
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
|
||||||
|
revertedKeys: make(map[blockstm.Key]struct{}),
|
||||||
refund: s.refund,
|
refund: s.refund,
|
||||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||||
logSize: s.logSize,
|
logSize: s.logSize,
|
||||||
|
|
@ -738,6 +1117,10 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
state.snapStorage[k] = temp
|
state.snapStorage[k] = temp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.mvHashmap != nil {
|
||||||
|
state.mvHashmap = s.mvHashmap
|
||||||
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"testing/quick"
|
"testing/quick"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
@ -488,6 +491,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.
|
// 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
|
// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512
|
||||||
func TestCopyOfCopy(t *testing.T) {
|
func TestCopyOfCopy(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -89,6 +90,6 @@ func precacheTransaction(msg types.Message, config *params.ChainConfig, gaspool
|
||||||
// Update the evm with the new transaction context.
|
// Update the evm with the new transaction context.
|
||||||
evm.Reset(NewEVMTxContext(msg), statedb)
|
evm.Reset(NewEVMTxContext(msg), statedb)
|
||||||
// Add addresses to access list if applicable
|
// Add addresses to access list if applicable
|
||||||
_, err := ApplyMessage(evm, msg, gaspool)
|
_, err := ApplyMessage(evm, msg, gaspool, context.Background())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
|
||||||
// Process returns the receipts and logs accumulated during the process and
|
// 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
|
// 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.
|
// 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 (
|
var (
|
||||||
receipts types.Receipts
|
receipts types.Receipts
|
||||||
usedGas = new(uint64)
|
usedGas = new(uint64)
|
||||||
|
|
@ -74,12 +75,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
|
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
|
||||||
// Iterate over and process the individual transactions
|
// Iterate over and process the individual transactions
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
|
if interruptCtx != nil {
|
||||||
|
select {
|
||||||
|
case <-interruptCtx.Done():
|
||||||
|
return nil, nil, 0, interruptCtx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
|
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||||
}
|
}
|
||||||
statedb.Prepare(tx.Hash(), i)
|
statedb.Prepare(tx.Hash(), i)
|
||||||
receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
|
receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, interruptCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||||
}
|
}
|
||||||
|
|
@ -92,17 +101,61 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
return receipts, allLogs, *usedGas, nil
|
return receipts, allLogs, *usedGas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
|
// nolint : unparam
|
||||||
|
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM, interruptCtx context.Context) (*types.Receipt, error) {
|
||||||
// Create a new context to be used in the EVM environment.
|
// Create a new context to be used in the EVM environment.
|
||||||
txContext := NewEVMTxContext(msg)
|
txContext := NewEVMTxContext(msg)
|
||||||
evm.Reset(txContext, statedb)
|
evm.Reset(txContext, statedb)
|
||||||
|
|
||||||
// Apply the transaction to the current state (included in the env).
|
var result *ExecutionResult
|
||||||
result, err := ApplyMessage(evm, msg, gp)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Update the state with pending changes.
|
// Update the state with pending changes.
|
||||||
var root []byte
|
var root []byte
|
||||||
if config.IsByzantium(blockNumber) {
|
if config.IsByzantium(blockNumber) {
|
||||||
|
|
@ -141,7 +194,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
|
||||||
// and uses the input parameters for its environment. It returns the receipt
|
// and uses the input parameters for its environment. It returns the receipt
|
||||||
// for the transaction, gas used and an error if the transaction failed,
|
// for the transaction, gas used and an error if the transaction failed,
|
||||||
// indicating the block was invalid.
|
// indicating the block was invalid.
|
||||||
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
|
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, interruptCtx context.Context) (*types.Receipt, error) {
|
||||||
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
|
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -149,5 +202,6 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
||||||
// Create a new context to be used in the EVM environment
|
// Create a new context to be used in the EVM environment
|
||||||
blockContext := NewEVMBlockContext(header, bc, author)
|
blockContext := NewEVMBlockContext(header, bc, author)
|
||||||
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
|
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
|
||||||
return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
|
||||||
|
return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv, interruptCtx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,8 +236,12 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
genesis = gspec.MustCommit(db)
|
genesis = gspec.MustCommit(db)
|
||||||
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
|
blockchain, _ = NewBlockChain(db, nil, gspec.Config, 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()
|
defer blockchain.Stop()
|
||||||
|
defer parallelBlockchain.Stop()
|
||||||
|
|
||||||
|
for _, bc := range []*BlockChain{blockchain, parallelBlockchain} {
|
||||||
for i, tt := range []struct {
|
for i, tt := range []struct {
|
||||||
txs []*types.Transaction
|
txs []*types.Transaction
|
||||||
want string
|
want string
|
||||||
|
|
@ -250,7 +254,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
|
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
|
||||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
_, err := bc.InsertChain(types.Blocks{block})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("block imported without errors")
|
t.Fatal("block imported without errors")
|
||||||
}
|
}
|
||||||
|
|
@ -259,6 +263,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrSenderNoEOA, for this we need the sender to have contract code
|
// ErrSenderNoEOA, for this we need the sender to have contract code
|
||||||
{
|
{
|
||||||
|
|
@ -276,8 +281,12 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
genesis = gspec.MustCommit(db)
|
genesis = gspec.MustCommit(db)
|
||||||
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
|
blockchain, _ = NewBlockChain(db, nil, gspec.Config, 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()
|
defer blockchain.Stop()
|
||||||
|
defer parallelBlockchain.Stop()
|
||||||
|
|
||||||
|
for _, bc := range []*BlockChain{blockchain, parallelBlockchain} {
|
||||||
for i, tt := range []struct {
|
for i, tt := range []struct {
|
||||||
txs []*types.Transaction
|
txs []*types.Transaction
|
||||||
want string
|
want string
|
||||||
|
|
@ -290,7 +299,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
|
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
|
||||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
_, err := bc.InsertChain(types.Blocks{block})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("block imported without errors")
|
t.Fatal("block imported without errors")
|
||||||
}
|
}
|
||||||
|
|
@ -299,6 +308,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be
|
// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -62,6 +63,11 @@ type StateTransition struct {
|
||||||
data []byte
|
data []byte
|
||||||
state vm.StateDB
|
state vm.StateDB
|
||||||
evm *vm.EVM
|
evm *vm.EVM
|
||||||
|
|
||||||
|
// If true, fee burning and tipping won't happen during transition. Instead, their values will be included in the
|
||||||
|
// ExecutionResult, which caller can use the values to update the balance of burner and coinbase account.
|
||||||
|
// This is useful during parallel state transition, where the common account read/write should be minimized.
|
||||||
|
noFeeBurnAndTip bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message represents a message sent to a contract.
|
// Message represents a message sent to a contract.
|
||||||
|
|
@ -87,6 +93,10 @@ type ExecutionResult struct {
|
||||||
UsedGas uint64 // Total used gas but include the refunded gas
|
UsedGas uint64 // Total used gas but include the refunded gas
|
||||||
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
|
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)
|
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
|
// Unwrap returns the internal evm error which allows us for further
|
||||||
|
|
@ -179,8 +189,15 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
|
||||||
// the gas used (which includes gas refunds) and an error if it failed. An error always
|
// the gas used (which includes gas refunds) and an error if it failed. An error always
|
||||||
// indicates a core error meaning that the message would always fail for that particular
|
// indicates a core error meaning that the message would always fail for that particular
|
||||||
// state and would never be accepted within a block.
|
// state and would never be accepted within a block.
|
||||||
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) {
|
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool, interruptCtx context.Context) (*ExecutionResult, error) {
|
||||||
return NewStateTransition(evm, msg, gp).TransitionDb()
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// to returns the recipient of the message.
|
// to returns the recipient of the message.
|
||||||
|
|
@ -274,9 +291,14 @@ func (st *StateTransition) preCheck() error {
|
||||||
//
|
//
|
||||||
// However if any consensus issue encountered, return the error directly with
|
// However if any consensus issue encountered, return the error directly with
|
||||||
// nil evm execution result.
|
// nil evm execution result.
|
||||||
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*ExecutionResult, error) {
|
||||||
input1 := st.state.GetBalance(st.msg.From())
|
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
|
// First check this message satisfies all consensus rules before
|
||||||
// applying the message. The rules include these clauses
|
// applying the message. The rules include these clauses
|
||||||
|
|
@ -327,7 +349,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
} else {
|
} else {
|
||||||
// Increment the nonce for the next transaction
|
// Increment the nonce for the next transaction
|
||||||
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
|
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
|
||||||
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
|
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value, interruptCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !london {
|
if !london {
|
||||||
|
|
@ -342,12 +364,23 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee))
|
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee))
|
||||||
}
|
}
|
||||||
amount := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)
|
amount := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)
|
||||||
|
|
||||||
|
var burnAmount *big.Int
|
||||||
|
|
||||||
|
var burntContractAddress common.Address
|
||||||
|
|
||||||
if london {
|
if london {
|
||||||
burntContractAddress := common.HexToAddress(st.evm.ChainConfig().Bor.CalculateBurntContract(st.evm.Context.BlockNumber.Uint64()))
|
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)
|
burnAmount = new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)
|
||||||
|
|
||||||
|
if !st.noFeeBurnAndTip {
|
||||||
st.state.AddBalance(burntContractAddress, burnAmount)
|
st.state.AddBalance(burntContractAddress, burnAmount)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !st.noFeeBurnAndTip {
|
||||||
st.state.AddBalance(st.evm.Context.Coinbase, amount)
|
st.state.AddBalance(st.evm.Context.Coinbase, amount)
|
||||||
|
|
||||||
output1 := new(big.Int).SetBytes(input1.Bytes())
|
output1 := new(big.Int).SetBytes(input1.Bytes())
|
||||||
output2 := new(big.Int).SetBytes(input2.Bytes())
|
output2 := new(big.Int).SetBytes(input2.Bytes())
|
||||||
|
|
||||||
|
|
@ -365,11 +398,16 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
output1.Sub(output1, amount),
|
output1.Sub(output1, amount),
|
||||||
output2.Add(output2, amount),
|
output2.Add(output2, amount),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return &ExecutionResult{
|
return &ExecutionResult{
|
||||||
UsedGas: st.gasUsed(),
|
UsedGas: st.gasUsed(),
|
||||||
Err: vmerr,
|
Err: vmerr,
|
||||||
ReturnData: ret,
|
ReturnData: ret,
|
||||||
|
SenderInitBalance: input1,
|
||||||
|
FeeBurnt: burnAmount,
|
||||||
|
BurntContractAddress: burntContractAddress,
|
||||||
|
FeeTipped: amount,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1815,7 +1815,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||||
|
|
||||||
chainConfig.LondonBlock = big.NewInt(0)
|
chainConfig.LondonBlock = big.NewInt(0)
|
||||||
|
|
||||||
_, back, closeFn := miner.NewTestWorker(t, chainConfig, engine, db, 0, 0, 0)
|
_, back, closeFn := miner.NewTestWorker(t, chainConfig, engine, db, 0, 0, 0, 0)
|
||||||
defer closeFn()
|
defer closeFn()
|
||||||
|
|
||||||
genesis := back.BlockChain().Genesis()
|
genesis := back.BlockChain().Genesis()
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
|
@ -47,5 +49,5 @@ type Processor interface {
|
||||||
// Process processes the state changes according to the Ethereum rules by running
|
// 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 transaction messages using the statedb and applying any rewards to both
|
||||||
// the processor (coinbase) and any included uncles.
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,11 @@ type Header struct {
|
||||||
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
||||||
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
BaseFee *big.Int `json:"baseFeePerGas" 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
|
TODO (MariusVanDerWijden) Add this field once needed
|
||||||
// Random was added during the merge and contains the BeaconState randomness
|
// Random was added during the merge and contains the BeaconState randomness
|
||||||
|
|
@ -252,6 +257,15 @@ func CopyHeader(h *Header) *Header {
|
||||||
cpy.Extra = make([]byte, len(h.Extra))
|
cpy.Extra = make([]byte, len(h.Extra))
|
||||||
copy(cpy.Extra, h.Extra)
|
copy(cpy.Extra, h.Extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return &cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,6 +321,7 @@ func (b *Block) TxHash() common.Hash { return b.header.TxHash }
|
||||||
func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash }
|
func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash }
|
||||||
func (b *Block) UncleHash() common.Hash { return b.header.UncleHash }
|
func (b *Block) UncleHash() common.Hash { return b.header.UncleHash }
|
||||||
func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) }
|
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 {
|
func (b *Block) BaseFee() *big.Int {
|
||||||
if b.header.BaseFee == nil {
|
if b.header.BaseFee == nil {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
func TestEIP1559BlockEncoding(t *testing.T) {
|
||||||
blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0")
|
blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0")
|
||||||
var block Block
|
var block Block
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
MixDigest common.Hash `json:"mixHash"`
|
MixDigest common.Hash `json:"mixHash"`
|
||||||
Nonce BlockNonce `json:"nonce"`
|
Nonce BlockNonce `json:"nonce"`
|
||||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||||
|
TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
|
||||||
Hash common.Hash `json:"hash"`
|
Hash common.Hash `json:"hash"`
|
||||||
}
|
}
|
||||||
var enc Header
|
var enc Header
|
||||||
|
|
@ -51,6 +52,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
enc.MixDigest = h.MixDigest
|
enc.MixDigest = h.MixDigest
|
||||||
enc.Nonce = h.Nonce
|
enc.Nonce = h.Nonce
|
||||||
enc.BaseFee = (*hexutil.Big)(h.BaseFee)
|
enc.BaseFee = (*hexutil.Big)(h.BaseFee)
|
||||||
|
enc.TxDependency = h.TxDependency
|
||||||
enc.Hash = h.Hash()
|
enc.Hash = h.Hash()
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +76,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
||||||
MixDigest *common.Hash `json:"mixHash"`
|
MixDigest *common.Hash `json:"mixHash"`
|
||||||
Nonce *BlockNonce `json:"nonce"`
|
Nonce *BlockNonce `json:"nonce"`
|
||||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||||
|
TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
|
||||||
}
|
}
|
||||||
var dec Header
|
var dec Header
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
|
@ -140,5 +143,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
||||||
if dec.BaseFee != nil {
|
if dec.BaseFee != nil {
|
||||||
h.BaseFee = (*big.Int)(dec.BaseFee)
|
h.BaseFee = (*big.Int)(dec.BaseFee)
|
||||||
}
|
}
|
||||||
|
if dec.TxDependency != nil {
|
||||||
|
h.TxDependency = dec.TxDependency
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||||
w.WriteBytes(obj.MixDigest[:])
|
w.WriteBytes(obj.MixDigest[:])
|
||||||
w.WriteBytes(obj.Nonce[:])
|
w.WriteBytes(obj.Nonce[:])
|
||||||
_tmp1 := obj.BaseFee != nil
|
_tmp1 := obj.BaseFee != nil
|
||||||
if _tmp1 {
|
_tmp2 := len(obj.TxDependency) > 0
|
||||||
|
if _tmp1 || _tmp2 {
|
||||||
if obj.BaseFee == nil {
|
if obj.BaseFee == nil {
|
||||||
w.Write(rlp.EmptyString)
|
w.Write(rlp.EmptyString)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -51,6 +52,17 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||||
w.WriteBigInt(obj.BaseFee)
|
w.WriteBigInt(obj.BaseFee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _tmp2 {
|
||||||
|
_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)
|
w.ListEnd(_tmp0)
|
||||||
return w.Flush()
|
return w.Flush()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -165,7 +166,7 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
|
||||||
// parameters. It also handles any necessary value transfer required and takes
|
// parameters. It also handles any necessary value transfer required and takes
|
||||||
// the necessary steps to create accounts and reverses the state in case of an
|
// the necessary steps to create accounts and reverses the state in case of an
|
||||||
// execution error or failed value transfer.
|
// execution error or failed value transfer.
|
||||||
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
|
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, interruptCtx context.Context) (ret []byte, leftOverGas uint64, err error) {
|
||||||
// Fail if we're trying to execute above the call depth limit
|
// Fail if we're trying to execute above the call depth limit
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
|
|
@ -225,7 +226,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
// The depth-check is already done, and precompiles handled above
|
// The depth-check is already done, and precompiles handled above
|
||||||
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
|
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.PreRun(contract, input, false, interruptCtx)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +283,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.PreRun(contract, input, false, nil)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -322,7 +323,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
// Initialise a new contract and make initialise the delegate values
|
// Initialise a new contract and make initialise the delegate values
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.PreRun(contract, input, false, nil)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -378,7 +379,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||||
// when we're in Homestead this also counts for code storage gas errors.
|
// when we're in Homestead this also counts for code storage gas errors.
|
||||||
ret, err = evm.interpreter.Run(contract, input, true)
|
ret, err = evm.interpreter.PreRun(contract, input, true, nil)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -450,7 +451,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
ret, err := evm.interpreter.Run(contract, nil, false)
|
ret, err := evm.interpreter.PreRun(contract, nil, false, nil)
|
||||||
|
|
||||||
// Check whether the max code size has been exceeded, assign err if the case.
|
// Check whether the max code size has been exceeded, assign err if the case.
|
||||||
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ func TestEIP2200(t *testing.T) {
|
||||||
}
|
}
|
||||||
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
||||||
|
|
||||||
_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int))
|
_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int), nil)
|
||||||
if err != tt.failure {
|
if err != tt.failure {
|
||||||
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
|
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -392,16 +392,21 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||||
// opExtCodeHash returns the code hash of a specified account.
|
// opExtCodeHash returns the code hash of a specified account.
|
||||||
// There are several cases when the function is called, while we can relay everything
|
// There are several cases when the function is called, while we can relay everything
|
||||||
// to `state.GetCodeHash` function to ensure the correctness.
|
// to `state.GetCodeHash` function to ensure the correctness.
|
||||||
|
//
|
||||||
// (1) Caller tries to get the code hash of a normal contract account, state
|
// (1) Caller tries to get the code hash of a normal contract account, state
|
||||||
|
//
|
||||||
// should return the relative code hash and set it as the result.
|
// should return the relative code hash and set it as the result.
|
||||||
//
|
//
|
||||||
// (2) Caller tries to get the code hash of a non-existent account, state should
|
// (2) Caller tries to get the code hash of a non-existent account, state should
|
||||||
|
//
|
||||||
// return common.Hash{} and zero will be set as the result.
|
// return common.Hash{} and zero will be set as the result.
|
||||||
//
|
//
|
||||||
// (3) Caller tries to get the code hash for an account without contract code,
|
// (3) Caller tries to get the code hash for an account without contract code,
|
||||||
|
//
|
||||||
// state should return emptyCodeHash(0xc5d246...) as the result.
|
// state should return emptyCodeHash(0xc5d246...) as the result.
|
||||||
//
|
//
|
||||||
// (4) Caller tries to get the code hash of a precompiled account, the result
|
// (4) Caller tries to get the code hash of a precompiled account, the result
|
||||||
|
//
|
||||||
// should be zero or emptyCodeHash.
|
// should be zero or emptyCodeHash.
|
||||||
//
|
//
|
||||||
// It is worth noting that in order to avoid unnecessary create and clean,
|
// It is worth noting that in order to avoid unnecessary create and clean,
|
||||||
|
|
@ -411,9 +416,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||||
// customized chain, the return value will be zero.
|
// customized chain, the return value will be zero.
|
||||||
//
|
//
|
||||||
// (5) Caller tries to get the code hash for an account which is marked as suicided
|
// (5) Caller tries to get the code hash for an account which is marked as suicided
|
||||||
|
//
|
||||||
// in the current transaction, the code hash of this account should be returned.
|
// in the current transaction, the code hash of this account should be returned.
|
||||||
//
|
//
|
||||||
// (6) Caller tries to get the code hash for an account which is marked as deleted,
|
// (6) Caller tries to get the code hash for an account which is marked as deleted,
|
||||||
|
//
|
||||||
// this account should be regarded as a non-existent account and zero should be returned.
|
// this account should be regarded as a non-existent account and zero should be returned.
|
||||||
func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
slot := scope.Stack.peek()
|
slot := scope.Stack.peek()
|
||||||
|
|
@ -688,7 +695,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
||||||
bigVal = value.ToBig()
|
bigVal = value.ToBig()
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)
|
ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
temp.Clear()
|
temp.Clear()
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,33 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
opcodeCommitInterruptCounter = metrics.NewRegisteredCounter("worker/opcodeCommitInterrupt", nil)
|
||||||
|
ErrInterrupt = errors.New("EVM execution interrupted")
|
||||||
|
ErrNoCache = errors.New("no tx cache found")
|
||||||
|
ErrNoCurrentTx = errors.New("no current tx found in interruptCtx")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// These are keys for the interruptCtx
|
||||||
|
InterruptCtxDelayKey = "delay"
|
||||||
|
InterruptCtxOpcodeDelayKey = "opcodeDelay"
|
||||||
|
|
||||||
|
// InterruptedTxCacheSize is size of lru cache for interrupted txs
|
||||||
|
InterruptedTxCacheSize = 90000
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config are the configuration options for the Interpreter
|
// Config are the configuration options for the Interpreter
|
||||||
|
|
@ -34,6 +56,10 @@ type Config struct {
|
||||||
JumpTable *JumpTable // EVM instruction table, automatically populated if unset
|
JumpTable *JumpTable // EVM instruction table, automatically populated if unset
|
||||||
|
|
||||||
ExtraEips []int // Additional EIPS that are to be enabled
|
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,
|
// ScopeContext contains the things that are per-call, such as stack and memory,
|
||||||
|
|
@ -64,6 +90,54 @@ type EVMInterpreter struct {
|
||||||
returnData []byte // Last CALL's return data for subsequent reuse
|
returnData []byte // Last CALL's return data for subsequent reuse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TxCacher is an wrapper of lru.cache for caching transactions that get interrupted
|
||||||
|
type TxCache struct {
|
||||||
|
Cache *lru.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
type txCacheKey struct{}
|
||||||
|
type InterruptedTxContext_currenttxKey struct{}
|
||||||
|
|
||||||
|
// SetCurrentTxOnContext sets the current tx on the context
|
||||||
|
func SetCurrentTxOnContext(ctx context.Context, txHash common.Hash) context.Context {
|
||||||
|
return context.WithValue(ctx, InterruptedTxContext_currenttxKey{}, txHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentTxFromContext gets the current tx from the context
|
||||||
|
func GetCurrentTxFromContext(ctx context.Context) (common.Hash, error) {
|
||||||
|
val := ctx.Value(InterruptedTxContext_currenttxKey{})
|
||||||
|
if val == nil {
|
||||||
|
return common.Hash{}, ErrNoCurrentTx
|
||||||
|
}
|
||||||
|
|
||||||
|
c, ok := val.(common.Hash)
|
||||||
|
if !ok {
|
||||||
|
return common.Hash{}, ErrNoCurrentTx
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCache returns the txCache from the context
|
||||||
|
func GetCache(ctx context.Context) (*TxCache, error) {
|
||||||
|
val := ctx.Value(txCacheKey{})
|
||||||
|
if val == nil {
|
||||||
|
return nil, ErrNoCache
|
||||||
|
}
|
||||||
|
|
||||||
|
c, ok := val.(*TxCache)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrNoCache
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutCache puts the txCache into the context
|
||||||
|
func PutCache(ctx context.Context, cache *TxCache) context.Context {
|
||||||
|
return context.WithValue(ctx, txCacheKey{}, cache)
|
||||||
|
}
|
||||||
|
|
||||||
// NewEVMInterpreter returns a new instance of the Interpreter.
|
// NewEVMInterpreter returns a new instance of the Interpreter.
|
||||||
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
||||||
// If jump table was not initialised we set the default one.
|
// If jump table was not initialised we set the default one.
|
||||||
|
|
@ -107,14 +181,196 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PreRun is a wrapper around Run that allows for a delay to be injected before each opcode when induced by tests else it calls the lagace Run() method
|
||||||
|
func (in *EVMInterpreter) PreRun(contract *Contract, input []byte, readOnly bool, interruptCtx context.Context) (ret []byte, err error) {
|
||||||
|
var opcodeDelay interface{}
|
||||||
|
|
||||||
|
if interruptCtx != nil {
|
||||||
|
if interruptCtx.Value(InterruptCtxOpcodeDelayKey) != nil {
|
||||||
|
opcodeDelay = interruptCtx.Value(InterruptCtxOpcodeDelayKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opcodeDelay != nil {
|
||||||
|
return in.RunWithDelay(contract, input, readOnly, interruptCtx, opcodeDelay.(uint))
|
||||||
|
}
|
||||||
|
|
||||||
|
return in.Run(contract, input, readOnly, interruptCtx)
|
||||||
|
}
|
||||||
|
|
||||||
// Run loops and evaluates the contract's code with the given input data and returns
|
// Run loops and evaluates the contract's code with the given input data and returns
|
||||||
// the return byte-slice and an error if one occurred.
|
// the return byte-slice and an error if one occurred.
|
||||||
//
|
//
|
||||||
// It's important to note that any errors returned by the interpreter should be
|
// It's important to note that any errors returned by the interpreter should be
|
||||||
// considered a revert-and-consume-all-gas operation except for
|
// considered a revert-and-consume-all-gas operation except for
|
||||||
// ErrExecutionReverted which means revert-and-keep-gas-left.
|
// ErrExecutionReverted which means revert-and-keep-gas-left.
|
||||||
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
|
// nolint: gocognit
|
||||||
|
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, interruptCtx context.Context) (ret []byte, err error) {
|
||||||
|
// Increment the call depth which is restricted to 1024
|
||||||
|
in.evm.depth++
|
||||||
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
||||||
|
// Make sure the readOnly is only set if we aren't in readOnly yet.
|
||||||
|
// This also makes sure that the readOnly flag isn't removed for child calls.
|
||||||
|
if readOnly && !in.readOnly {
|
||||||
|
in.readOnly = true
|
||||||
|
defer func() { in.readOnly = false }()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the previous call's return data. It's unimportant to preserve the old buffer
|
||||||
|
// as every returning call will return new data anyway.
|
||||||
|
in.returnData = nil
|
||||||
|
|
||||||
|
// Don't bother with the execution if there's no code.
|
||||||
|
if len(contract.Code) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
op OpCode // current opcode
|
||||||
|
mem = NewMemory() // bound memory
|
||||||
|
stack = newstack() // local stack
|
||||||
|
callContext = &ScopeContext{
|
||||||
|
Memory: mem,
|
||||||
|
Stack: stack,
|
||||||
|
Contract: contract,
|
||||||
|
}
|
||||||
|
// For optimisation reason we're using uint64 as the program counter.
|
||||||
|
// It's theoretically possible to go above 2^64. The YP defines the PC
|
||||||
|
// to be uint256. Practically much less so feasible.
|
||||||
|
pc = uint64(0) // program counter
|
||||||
|
cost uint64
|
||||||
|
// copies used by tracer
|
||||||
|
pcCopy uint64 // needed for the deferred EVMLogger
|
||||||
|
gasCopy uint64 // for EVMLogger to log gas remaining before execution
|
||||||
|
logged bool // deferred EVMLogger should ignore already logged steps
|
||||||
|
res []byte // result of the opcode execution function
|
||||||
|
)
|
||||||
|
// Don't move this deferrred function, it's placed before the capturestate-deferred method,
|
||||||
|
// so that it get's executed _after_: the capturestate needs the stacks before
|
||||||
|
// they are returned to the pools
|
||||||
|
defer func() {
|
||||||
|
returnStack(stack)
|
||||||
|
}()
|
||||||
|
|
||||||
|
contract.Input = input
|
||||||
|
|
||||||
|
if in.cfg.Debug {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
if !logged {
|
||||||
|
in.cfg.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||||
|
} else {
|
||||||
|
in.cfg.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// The Interpreter main run loop (contextual). This loop runs until either an
|
||||||
|
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
||||||
|
// the execution of one of the operations or until the done flag is set by the
|
||||||
|
// parent context.
|
||||||
|
for {
|
||||||
|
if interruptCtx != nil {
|
||||||
|
// case of interrupting by timeout
|
||||||
|
select {
|
||||||
|
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)
|
||||||
|
if found {
|
||||||
|
interruptedTxCache.Cache.Remove(txHash)
|
||||||
|
} else {
|
||||||
|
// if the tx is not in the cache, it means that it has not been interrupted before and we will interrupt it
|
||||||
|
opcodeCommitInterruptCounter.Inc(1)
|
||||||
|
log.Warn("OPCODE Level interrupt")
|
||||||
|
|
||||||
|
return nil, ErrInterrupt
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.cfg.Debug {
|
||||||
|
// Capture pre-execution values for tracing.
|
||||||
|
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||||
|
}
|
||||||
|
// Get the operation from the jump table and validate the stack to ensure there are
|
||||||
|
// enough stack items available to perform the operation.
|
||||||
|
op = contract.GetOp(pc)
|
||||||
|
operation := in.cfg.JumpTable[op]
|
||||||
|
cost = operation.constantGas // For tracing
|
||||||
|
// Validate stack
|
||||||
|
if sLen := stack.len(); sLen < operation.minStack {
|
||||||
|
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
|
||||||
|
} else if sLen > operation.maxStack {
|
||||||
|
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contract.UseGas(cost) {
|
||||||
|
return nil, ErrOutOfGas
|
||||||
|
}
|
||||||
|
// nolint : nestif
|
||||||
|
if operation.dynamicGas != nil {
|
||||||
|
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||||
|
var memorySize uint64
|
||||||
|
// calculate the new memory size and expand the memory to fit
|
||||||
|
// the operation
|
||||||
|
// Memory check needs to be done prior to evaluating the dynamic gas portion,
|
||||||
|
// to detect calculation overflows
|
||||||
|
if operation.memorySize != nil {
|
||||||
|
memSize, overflow := operation.memorySize(stack)
|
||||||
|
if overflow {
|
||||||
|
return nil, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
// memory is expanded in words of 32 bytes. Gas
|
||||||
|
// is also calculated in words.
|
||||||
|
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
||||||
|
return nil, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Consume the gas and return an error if not enough gas is available.
|
||||||
|
// cost is explicitly set so that the capture state defer method can get the proper cost
|
||||||
|
var dynamicCost uint64
|
||||||
|
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
|
||||||
|
cost += dynamicCost // for tracing
|
||||||
|
if err != nil || !contract.UseGas(dynamicCost) {
|
||||||
|
return nil, ErrOutOfGas
|
||||||
|
}
|
||||||
|
if memorySize > 0 {
|
||||||
|
mem.Resize(memorySize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.cfg.Debug {
|
||||||
|
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||||
|
|
||||||
|
logged = true
|
||||||
|
}
|
||||||
|
// execute the operation
|
||||||
|
res, err = operation.execute(&pc, in, callContext)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pc++
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == errStopToken {
|
||||||
|
err = nil // clear stop token error
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: gocognit
|
||||||
|
// RunWithDelay is Run() with a delay between each opcode. Only used by testcases.
|
||||||
|
func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnly bool, interruptCtx context.Context, opcodeDelay uint) (ret []byte, err error) {
|
||||||
// Increment the call depth which is restricted to 1024
|
// Increment the call depth which is restricted to 1024
|
||||||
in.evm.depth++
|
in.evm.depth++
|
||||||
defer func() { in.evm.depth-- }()
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
@ -179,6 +435,36 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
// the execution of one of the operations or until the done flag is set by the
|
// the execution of one of the operations or until the done flag is set by the
|
||||||
// parent context.
|
// parent context.
|
||||||
for {
|
for {
|
||||||
|
if interruptCtx != nil {
|
||||||
|
// case of interrupting by timeout
|
||||||
|
select {
|
||||||
|
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)
|
||||||
|
|
||||||
|
if found {
|
||||||
|
interruptedTxCache.Cache.Remove(txHash)
|
||||||
|
} else {
|
||||||
|
// if the tx is not in the cache, it means that it has not been interrupted before and we will interrupt it
|
||||||
|
opcodeCommitInterruptCounter.Inc(1)
|
||||||
|
log.Warn("OPCODE Level interrupt")
|
||||||
|
|
||||||
|
return nil, ErrInterrupt
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(opcodeDelay) * time.Millisecond)
|
||||||
|
|
||||||
if in.cfg.Debug {
|
if in.cfg.Debug {
|
||||||
// Capture pre-execution values for tracing.
|
// Capture pre-execution values for tracing.
|
||||||
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ func TestLoopInterrupt(t *testing.T) {
|
||||||
timeout := make(chan bool)
|
timeout := make(chan bool)
|
||||||
|
|
||||||
go func(evm *EVM) {
|
go func(evm *EVM) {
|
||||||
_, _, err := evm.Call(AccountRef(common.Address{}), address, nil, math.MaxUint64, new(big.Int))
|
_, _, err := evm.Call(AccountRef(common.Address{}), address, nil, math.MaxUint64, new(big.Int), nil)
|
||||||
errChannel <- err
|
errChannel <- err
|
||||||
}(evm)
|
}(evm)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||||
input,
|
input,
|
||||||
cfg.GasLimit,
|
cfg.GasLimit,
|
||||||
cfg.Value,
|
cfg.Value,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
return ret, cfg.State, err
|
return ret, cfg.State, err
|
||||||
|
|
@ -186,6 +187,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
|
||||||
input,
|
input,
|
||||||
cfg.GasLimit,
|
cfg.GasLimit,
|
||||||
cfg.Value,
|
cfg.Value,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
return ret, leftOverGas, err
|
return ret, leftOverGas, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -386,12 +386,15 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
|
||||||
//cfg.State.CreateAccount(cfg.Origin)
|
//cfg.State.CreateAccount(cfg.Origin)
|
||||||
// set the receiver's (the executing contract) code for execution.
|
// set the receiver's (the executing contract) code for execution.
|
||||||
cfg.State.SetCode(destination, code)
|
cfg.State.SetCode(destination, code)
|
||||||
vmenv.Call(sender, destination, nil, gas, cfg.Value)
|
|
||||||
|
// nolint: errcheck
|
||||||
|
vmenv.Call(sender, destination, nil, gas, cfg.Value, nil)
|
||||||
|
|
||||||
b.Run(name, func(b *testing.B) {
|
b.Run(name, func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
vmenv.Call(sender, destination, nil, gas, cfg.Value)
|
// nolint: errcheck
|
||||||
|
vmenv.Call(sender, destination, nil, gas, cfg.Value, nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ devfakeauthor = false # Run miner without validator set authorization
|
||||||
netrestrict = "" # Restricts network communication to the given IP networks (CIDR masks)
|
netrestrict = "" # Restricts network communication to the given IP networks (CIDR masks)
|
||||||
nodekey = "" # P2P node key file
|
nodekey = "" # P2P node key file
|
||||||
nodekeyhex = "" # P2P node key as hex
|
nodekeyhex = "" # P2P node key as hex
|
||||||
|
txarrivalwait = "500ms" # Maximum duration to wait before requesting an announced transaction
|
||||||
[p2p.discovery]
|
[p2p.discovery]
|
||||||
v5disc = false # Enables the experimental RLPx V5 (Topic Discovery) mechanism
|
v5disc = false # Enables the experimental RLPx V5 (Topic Discovery) mechanism
|
||||||
bootnodes = [] # Comma separated enode URLs for P2P discovery bootstrap
|
bootnodes = [] # Comma separated enode URLs for P2P discovery bootstrap
|
||||||
|
|
@ -72,6 +73,7 @@ devfakeauthor = false # Run miner without validator set authorization
|
||||||
gaslimit = 30000000 # Target gas ceiling for mined blocks
|
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)
|
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
|
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]
|
[jsonrpc]
|
||||||
ipcdisable = false # Disable the IPC-RPC server
|
ipcdisable = false # Disable the IPC-RPC server
|
||||||
|
|
@ -128,7 +130,7 @@ devfakeauthor = false # Run miner without validator set authorization
|
||||||
metrics = false # Enable metrics collection and reporting
|
metrics = false # Enable metrics collection and reporting
|
||||||
expensive = false # Enable expensive metrics collection and reporting
|
expensive = false # Enable expensive metrics collection and reporting
|
||||||
prometheus-addr = "127.0.0.1:7071" # Address for Prometheus Server
|
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]
|
[telemetry.influx]
|
||||||
influxdb = false # Enable metrics export/push to an external InfluxDB database (v1)
|
influxdb = false # Enable metrics export/push to an external InfluxDB database (v1)
|
||||||
endpoint = "" # InfluxDB API endpoint to report metrics to
|
endpoint = "" # InfluxDB API endpoint to report metrics to
|
||||||
|
|
|
||||||
|
|
@ -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)
|
- ```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)
|
- ```dev.gaslimit```: Initial block gas limit (default: 11500000)
|
||||||
|
|
||||||
- ```pprof```: Enable the pprof HTTP server (default: false)
|
- ```pprof```: Enable the pprof HTTP server (default: false)
|
||||||
|
|
@ -216,6 +220,8 @@ The ```bor server``` command runs the Bor client.
|
||||||
|
|
||||||
- ```v5disc```: Enables the experimental RLPx V5 (Topic Discovery) mechanism (default: false)
|
- ```v5disc```: Enables the experimental RLPx V5 (Topic Discovery) mechanism (default: false)
|
||||||
|
|
||||||
|
- ```txarrivalwait```: Maximum duration to wait for a transaction before explicitly requesting it (defaults to 500ms) (default: 500ms)
|
||||||
|
|
||||||
### Sealer Options
|
### Sealer Options
|
||||||
|
|
||||||
- ```mine```: Enable mining (default: false)
|
- ```mine```: Enable mining (default: false)
|
||||||
|
|
@ -230,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.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
|
### Telemetry Options
|
||||||
|
|
||||||
- ```metrics```: Enable metrics collection and reporting (default: false)
|
- ```metrics```: Enable metrics collection and reporting (default: false)
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
var (
|
var (
|
||||||
vmConfig = vm.Config{
|
vmConfig = vm.Config{
|
||||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||||
|
ParallelEnable: config.ParallelEVM.Enable,
|
||||||
|
ParallelSpeculativeProcesses: config.ParallelEVM.SpeculativeProcesses,
|
||||||
}
|
}
|
||||||
cacheConfig = &core.CacheConfig{
|
cacheConfig = &core.CacheConfig{
|
||||||
TrieCleanLimit: config.TrieCleanCache,
|
TrieCleanLimit: config.TrieCleanCache,
|
||||||
|
|
@ -226,7 +228,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
|
|
||||||
checker := whitelist.NewService(10)
|
checker := whitelist.NewService(10)
|
||||||
|
|
||||||
|
// check if Parallel EVM is enabled
|
||||||
|
// if enabled, use parallel state processor
|
||||||
|
if config.ParallelEVM.Enable {
|
||||||
|
eth.blockchain, err = core.NewParallelBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, checker)
|
||||||
|
} else {
|
||||||
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, checker)
|
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, checker)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -268,6 +277,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
EthAPI: ethAPI,
|
EthAPI: ethAPI,
|
||||||
PeerRequiredBlocks: config.PeerRequiredBlocks,
|
PeerRequiredBlocks: config.PeerRequiredBlocks,
|
||||||
checker: checker,
|
checker: checker,
|
||||||
|
txArrivalWait: eth.p2pServer.TxArrivalWait,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1500,9 +1500,9 @@ func TestFakedSyncProgress66NoRemoteCheckpoint(t *testing.T) {
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
validate := func(count int) (bool, error) {
|
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 {
|
if count == 0 {
|
||||||
return false, whitelist.ErrNoRemoteCheckoint
|
return false, whitelist.ErrNoRemoteCheckpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
@ -1518,7 +1518,7 @@ func TestFakedSyncProgress66NoRemoteCheckpoint(t *testing.T) {
|
||||||
// Synchronise with the peer and make sure all blocks were retrieved
|
// Synchronise with the peer and make sure all blocks were retrieved
|
||||||
// Should fail in first attempt
|
// Should fail in first attempt
|
||||||
if err := tester.sync("light", nil, mode); err != nil {
|
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
|
// Try syncing again, should succeed
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func NewService(maxCapacity uint) *Service {
|
||||||
var (
|
var (
|
||||||
ErrCheckpointMismatch = errors.New("checkpoint mismatch")
|
ErrCheckpointMismatch = errors.New("checkpoint mismatch")
|
||||||
ErrLongFutureChain = errors.New("received future chain of unacceptable length")
|
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
|
// 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
|
// 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)
|
headers, hashes, err := fetchHeadersByNumber(lastCheckpointBlockNum, 1, 0, false)
|
||||||
if err != nil {
|
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 {
|
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()
|
reqBlockNum := headers[0].Number.Uint64()
|
||||||
|
|
|
||||||
|
|
@ -64,14 +64,14 @@ func TestIsValidPeer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// case2: false fetchHeadersByNumber function provided, should consider the chain as invalid
|
// 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)
|
res, err = s.IsValidPeer(nil, falseFetchHeadersByNumber)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error, got nil")
|
t.Fatal("expected error, got nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.Is(err, ErrNoRemoteCheckoint) {
|
if !errors.Is(err, ErrNoRemoteCheckpoint) {
|
||||||
t.Fatalf("expected error ErrNoRemoteCheckoint, got %v", err)
|
t.Fatalf("expected error ErrNoRemoteCheckpoint, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, res, false, "expected chain to be invalid")
|
require.Equal(t, res, false, "expected chain to be invalid")
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,9 @@ type Config struct {
|
||||||
// Bor logs flag
|
// Bor logs flag
|
||||||
BorLogs bool
|
BorLogs bool
|
||||||
|
|
||||||
|
// Parallel EVM (Block-STM) related config
|
||||||
|
ParallelEVM core.ParallelEVMConfig `toml:",omitempty"`
|
||||||
|
|
||||||
// Arrow Glacier block override (TODO: remove after the fork)
|
// Arrow Glacier block override (TODO: remove after the fork)
|
||||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,13 @@ const (
|
||||||
// re-request them.
|
// re-request them.
|
||||||
maxTxUnderpricedSetSize = 32768
|
maxTxUnderpricedSetSize = 32768
|
||||||
|
|
||||||
// txArriveTimeout is the time allowance before an announced transaction is
|
|
||||||
// explicitly requested.
|
|
||||||
txArriveTimeout = 500 * time.Millisecond
|
|
||||||
|
|
||||||
// txGatherSlack is the interval used to collate almost-expired announces
|
// txGatherSlack is the interval used to collate almost-expired announces
|
||||||
// with network fetches.
|
// with network fetches.
|
||||||
txGatherSlack = 100 * time.Millisecond
|
txGatherSlack = 100 * time.Millisecond
|
||||||
|
|
||||||
|
// maxTxArrivalWait is the longest acceptable duration for the txArrivalWait
|
||||||
|
// configuration value. Longer config values will default to this.
|
||||||
|
maxTxArrivalWait = 500 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -176,19 +176,21 @@ type TxFetcher struct {
|
||||||
step chan struct{} // Notification channel when the fetcher loop iterates
|
step chan struct{} // Notification channel when the fetcher loop iterates
|
||||||
clock mclock.Clock // Time wrapper to simulate in tests
|
clock mclock.Clock // Time wrapper to simulate in tests
|
||||||
rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random)
|
rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random)
|
||||||
|
|
||||||
|
txArrivalWait time.Duration // txArrivalWait is the time allowance before an announced transaction is explicitly requested.
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
||||||
// based on hash announcements.
|
// based on hash announcements.
|
||||||
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error) *TxFetcher {
|
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, txArrivalWait time.Duration) *TxFetcher {
|
||||||
return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, mclock.System{}, nil)
|
return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, mclock.System{}, nil, txArrivalWait)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
||||||
// a simulated version and the internal randomness with a deterministic one.
|
// a simulated version and the internal randomness with a deterministic one.
|
||||||
func NewTxFetcherForTests(
|
func NewTxFetcherForTests(
|
||||||
hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error,
|
hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error,
|
||||||
clock mclock.Clock, rand *mrand.Rand) *TxFetcher {
|
clock mclock.Clock, rand *mrand.Rand, txArrivalWait time.Duration) *TxFetcher {
|
||||||
return &TxFetcher{
|
return &TxFetcher{
|
||||||
notify: make(chan *txAnnounce),
|
notify: make(chan *txAnnounce),
|
||||||
cleanup: make(chan *txDelivery),
|
cleanup: make(chan *txDelivery),
|
||||||
|
|
@ -208,6 +210,7 @@ func NewTxFetcherForTests(
|
||||||
fetchTxs: fetchTxs,
|
fetchTxs: fetchTxs,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
rand: rand,
|
rand: rand,
|
||||||
|
txArrivalWait: txArrivalWait,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -333,6 +336,16 @@ func (f *TxFetcher) Drop(peer string) error {
|
||||||
// Start boots up the announcement based synchroniser, accepting and processing
|
// Start boots up the announcement based synchroniser, accepting and processing
|
||||||
// hash notifications and block fetches until termination requested.
|
// hash notifications and block fetches until termination requested.
|
||||||
func (f *TxFetcher) Start() {
|
func (f *TxFetcher) Start() {
|
||||||
|
// the txArrivalWait duration should not be less than the txGatherSlack duration
|
||||||
|
if f.txArrivalWait < txGatherSlack {
|
||||||
|
f.txArrivalWait = txGatherSlack
|
||||||
|
}
|
||||||
|
|
||||||
|
// the txArrivalWait duration should not be greater than the maxTxArrivalWait duration
|
||||||
|
if f.txArrivalWait > maxTxArrivalWait {
|
||||||
|
f.txArrivalWait = maxTxArrivalWait
|
||||||
|
}
|
||||||
|
|
||||||
go f.loop()
|
go f.loop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,6 +363,9 @@ func (f *TxFetcher) loop() {
|
||||||
waitTrigger = make(chan struct{}, 1)
|
waitTrigger = make(chan struct{}, 1)
|
||||||
timeoutTrigger = make(chan struct{}, 1)
|
timeoutTrigger = make(chan struct{}, 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log.Info("TxFetcher", "txArrivalWait", f.txArrivalWait.String())
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case ann := <-f.notify:
|
case ann := <-f.notify:
|
||||||
|
|
@ -441,7 +457,7 @@ func (f *TxFetcher) loop() {
|
||||||
// ones into the retrieval queues
|
// ones into the retrieval queues
|
||||||
actives := make(map[string]struct{})
|
actives := make(map[string]struct{})
|
||||||
for hash, instance := range f.waittime {
|
for hash, instance := range f.waittime {
|
||||||
if time.Duration(f.clock.Now()-instance)+txGatherSlack > txArriveTimeout {
|
if time.Duration(f.clock.Now()-instance)+txGatherSlack > f.txArrivalWait {
|
||||||
// Transaction expired without propagation, schedule for retrieval
|
// Transaction expired without propagation, schedule for retrieval
|
||||||
if f.announced[hash] != nil {
|
if f.announced[hash] != nil {
|
||||||
panic("announce tracker already contains waitlist item")
|
panic("announce tracker already contains waitlist item")
|
||||||
|
|
@ -698,14 +714,16 @@ func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
|
||||||
for _, instance := range f.waittime {
|
for _, instance := range f.waittime {
|
||||||
if earliest > instance {
|
if earliest > instance {
|
||||||
earliest = instance
|
earliest = instance
|
||||||
if txArriveTimeout-time.Duration(now-earliest) < gatherSlack {
|
if f.txArrivalWait-time.Duration(now-earliest) < gatherSlack {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*timer = f.clock.AfterFunc(txArriveTimeout-time.Duration(now-earliest), func() {
|
|
||||||
trigger <- struct{}{}
|
*timer = f.clock.AfterFunc(
|
||||||
})
|
f.txArrivalWait-time.Duration(now-earliest),
|
||||||
|
func() { trigger <- struct{}{} },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rescheduleTimeout iterates over all the transactions currently in flight and
|
// rescheduleTimeout iterates over all the transactions currently in flight and
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ var (
|
||||||
}
|
}
|
||||||
// testTxsHashes is the hashes of the test transactions above
|
// testTxsHashes is the hashes of the test transactions above
|
||||||
testTxsHashes = []common.Hash{testTxs[0].Hash(), testTxs[1].Hash(), testTxs[2].Hash(), testTxs[3].Hash()}
|
testTxsHashes = []common.Hash{testTxs[0].Hash(), testTxs[1].Hash(), testTxs[2].Hash(), testTxs[3].Hash()}
|
||||||
|
testTxArrivalWait = 500 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
type doTxNotify struct {
|
type doTxNotify struct {
|
||||||
|
|
@ -81,6 +82,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
nil,
|
nil,
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -113,7 +115,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
|
||||||
|
|
||||||
// Wait for the arrival timeout which should move all expired items
|
// Wait for the arrival timeout which should move all expired items
|
||||||
// from the wait list to the scheduler
|
// from the wait list to the scheduler
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -132,7 +134,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
|
||||||
isWaiting(map[string][]common.Hash{
|
isWaiting(map[string][]common.Hash{
|
||||||
"C": {{0x06}, {0x07}},
|
"C": {{0x06}, {0x07}},
|
||||||
}),
|
}),
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
"A": {{0x01}, {0x02}, {0x03}, {0x05}},
|
"A": {{0x01}, {0x02}, {0x03}, {0x05}},
|
||||||
|
|
@ -171,6 +173,7 @@ func TestTransactionFetcherSkipWaiting(t *testing.T) {
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
nil,
|
nil,
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -181,7 +184,7 @@ func TestTransactionFetcherSkipWaiting(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -234,6 +237,7 @@ func TestTransactionFetcherSingletonRequesting(t *testing.T) {
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
nil,
|
nil,
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -244,7 +248,7 @@ func TestTransactionFetcherSingletonRequesting(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -268,7 +272,7 @@ func TestTransactionFetcherSingletonRequesting(t *testing.T) {
|
||||||
"A": {{0x01}, {0x02}},
|
"A": {{0x01}, {0x02}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -314,6 +318,7 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
|
||||||
<-proceed
|
<-proceed
|
||||||
return errors.New("peer disconnected")
|
return errors.New("peer disconnected")
|
||||||
},
|
},
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -324,7 +329,7 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -383,6 +388,7 @@ func TestTransactionFetcherCleanup(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -393,7 +399,7 @@ func TestTransactionFetcherCleanup(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -422,6 +428,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -432,7 +439,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -460,6 +467,7 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -470,7 +478,7 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -506,6 +514,7 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -516,7 +525,7 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -544,14 +553,15 @@ func TestTransactionFetcherBroadcasts(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Set up three transactions to be in different stats, waiting, queued and fetching
|
// Set up three transactions to be in different stats, waiting, queued and fetching
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}},
|
||||||
|
|
||||||
isWaiting(map[string][]common.Hash{
|
isWaiting(map[string][]common.Hash{
|
||||||
|
|
@ -592,6 +602,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
nil,
|
nil,
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -600,7 +611,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
|
||||||
"A": {{0x01}},
|
"A": {{0x01}},
|
||||||
}),
|
}),
|
||||||
isScheduled{nil, nil, nil},
|
isScheduled{nil, nil, nil},
|
||||||
doWait{time: txArriveTimeout / 2, step: false},
|
doWait{time: testTxArrivalWait / 2, step: false},
|
||||||
isWaiting(map[string][]common.Hash{
|
isWaiting(map[string][]common.Hash{
|
||||||
"A": {{0x01}},
|
"A": {{0x01}},
|
||||||
}),
|
}),
|
||||||
|
|
@ -611,7 +622,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
|
||||||
"A": {{0x01}, {0x02}},
|
"A": {{0x01}, {0x02}},
|
||||||
}),
|
}),
|
||||||
isScheduled{nil, nil, nil},
|
isScheduled{nil, nil, nil},
|
||||||
doWait{time: txArriveTimeout / 2, step: true},
|
doWait{time: testTxArrivalWait / 2, step: true},
|
||||||
isWaiting(map[string][]common.Hash{
|
isWaiting(map[string][]common.Hash{
|
||||||
"A": {{0x02}},
|
"A": {{0x02}},
|
||||||
}),
|
}),
|
||||||
|
|
@ -624,7 +635,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout / 2, step: true},
|
doWait{time: testTxArrivalWait / 2, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -649,6 +660,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -659,7 +671,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
isScheduled{tracking: nil, fetching: nil},
|
isScheduled{tracking: nil, fetching: nil},
|
||||||
|
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -681,7 +693,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
|
||||||
},
|
},
|
||||||
// Ensure that followup announcements don't get scheduled
|
// Ensure that followup announcements don't get scheduled
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
"A": {testTxsHashes[1]},
|
"A": {testTxsHashes[1]},
|
||||||
|
|
@ -714,13 +726,14 @@ func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
nil,
|
nil,
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "B", hashes: []common.Hash{{0x02}}},
|
doTxNotify{peer: "B", hashes: []common.Hash{{0x02}}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
|
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
|
|
@ -733,7 +746,7 @@ func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
|
||||||
"B": {{0x02}},
|
"B": {{0x02}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
doWait{time: txFetchTimeout - txArriveTimeout, step: true},
|
doWait{time: txFetchTimeout - testTxArrivalWait, step: true},
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
"B": {{0x02}},
|
"B": {{0x02}},
|
||||||
|
|
@ -745,7 +758,7 @@ func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
|
||||||
"A": {},
|
"A": {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: nil,
|
tracking: nil,
|
||||||
fetching: nil,
|
fetching: nil,
|
||||||
|
|
@ -773,13 +786,14 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
nil,
|
nil,
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Announce all the transactions, wait a bit and ensure only a small
|
// Announce all the transactions, wait a bit and ensure only a small
|
||||||
// percentage gets requested
|
// percentage gets requested
|
||||||
doTxNotify{peer: "A", hashes: hashes},
|
doTxNotify{peer: "A", hashes: hashes},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -811,13 +825,14 @@ func TestTransactionFetcherDoSProtection(t *testing.T) {
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
nil,
|
nil,
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Announce half of the transaction and wait for them to be scheduled
|
// Announce half of the transaction and wait for them to be scheduled
|
||||||
doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2]},
|
doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2]},
|
||||||
doTxNotify{peer: "B", hashes: hashesB[:maxTxAnnounces/2-1]},
|
doTxNotify{peer: "B", hashes: hashesB[:maxTxAnnounces/2-1]},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
|
|
||||||
// Announce the second half and keep them in the wait list
|
// Announce the second half and keep them in the wait list
|
||||||
doTxNotify{peer: "A", hashes: hashesA[maxTxAnnounces/2 : maxTxAnnounces]},
|
doTxNotify{peer: "A", hashes: hashesA[maxTxAnnounces/2 : maxTxAnnounces]},
|
||||||
|
|
@ -878,12 +893,13 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
||||||
return errs
|
return errs
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Deliver a transaction through the fetcher, but reject as underpriced
|
// Deliver a transaction through the fetcher, but reject as underpriced
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}, direct: true},
|
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}, direct: true},
|
||||||
isScheduled{nil, nil, nil},
|
isScheduled{nil, nil, nil},
|
||||||
|
|
||||||
|
|
@ -921,7 +937,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
|
||||||
steps = append(steps, isWaiting(map[string][]common.Hash{
|
steps = append(steps, isWaiting(map[string][]common.Hash{
|
||||||
"A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals],
|
"A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals],
|
||||||
}))
|
}))
|
||||||
steps = append(steps, doWait{time: txArriveTimeout, step: true})
|
steps = append(steps, doWait{time: testTxArrivalWait, step: true})
|
||||||
steps = append(steps, isScheduled{
|
steps = append(steps, isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
"A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals],
|
"A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals],
|
||||||
|
|
@ -947,12 +963,13 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
|
||||||
return errs
|
return errs
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: append(steps, []interface{}{
|
steps: append(steps, []interface{}{
|
||||||
// The preparation of the test has already been done in `steps`, add the last check
|
// The preparation of the test has already been done in `steps`, add the last check
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{hashes[maxTxUnderpricedSetSize]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{hashes[maxTxUnderpricedSetSize]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxEnqueue{peer: "A", txs: []*types.Transaction{txs[maxTxUnderpricedSetSize]}, direct: true},
|
doTxEnqueue{peer: "A", txs: []*types.Transaction{txs[maxTxUnderpricedSetSize]}, direct: true},
|
||||||
isUnderpriced(maxTxUnderpricedSetSize),
|
isUnderpriced(maxTxUnderpricedSetSize),
|
||||||
}...),
|
}...),
|
||||||
|
|
@ -969,6 +986,7 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -981,9 +999,9 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
|
||||||
|
|
||||||
// Set up a few hashes into various stages
|
// Set up a few hashes into various stages
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}},
|
||||||
|
|
||||||
isWaiting(map[string][]common.Hash{
|
isWaiting(map[string][]common.Hash{
|
||||||
|
|
@ -1022,14 +1040,15 @@ func TestTransactionFetcherDrop(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Set up a few hashes into various stages
|
// Set up a few hashes into various stages
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x02}}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x02}}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x03}}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x03}}},
|
||||||
|
|
||||||
isWaiting(map[string][]common.Hash{
|
isWaiting(map[string][]common.Hash{
|
||||||
|
|
@ -1050,7 +1069,7 @@ func TestTransactionFetcherDrop(t *testing.T) {
|
||||||
|
|
||||||
// Push the node into a dangling (timeout) state
|
// Push the node into a dangling (timeout) state
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
isScheduled{
|
isScheduled{
|
||||||
tracking: map[string][]common.Hash{
|
tracking: map[string][]common.Hash{
|
||||||
|
|
@ -1088,12 +1107,13 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Set up a few hashes into various stages
|
// Set up a few hashes into various stages
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxNotify{peer: "B", hashes: []common.Hash{{0x01}}},
|
doTxNotify{peer: "B", hashes: []common.Hash{{0x01}}},
|
||||||
|
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
|
|
@ -1133,12 +1153,13 @@ func TestTransactionFetcherFuzzCrash01(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Get a transaction into fetching mode and make it dangling with a broadcast
|
// Get a transaction into fetching mode and make it dangling with a broadcast
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
|
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
|
||||||
|
|
||||||
// Notify the dangling transaction once more and crash via a timeout
|
// Notify the dangling transaction once more and crash via a timeout
|
||||||
|
|
@ -1160,17 +1181,18 @@ func TestTransactionFetcherFuzzCrash02(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Get a transaction into fetching mode and make it dangling with a broadcast
|
// Get a transaction into fetching mode and make it dangling with a broadcast
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
|
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
|
||||||
|
|
||||||
// Notify the dangling transaction once more, re-fetch, and crash via a drop and timeout
|
// Notify the dangling transaction once more, re-fetch, and crash via a drop and timeout
|
||||||
doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doDrop("A"),
|
doDrop("A"),
|
||||||
doWait{time: txFetchTimeout, step: true},
|
doWait{time: txFetchTimeout, step: true},
|
||||||
},
|
},
|
||||||
|
|
@ -1189,6 +1211,7 @@ func TestTransactionFetcherFuzzCrash03(t *testing.T) {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
|
|
@ -1199,7 +1222,7 @@ func TestTransactionFetcherFuzzCrash03(t *testing.T) {
|
||||||
|
|
||||||
// Notify the dangling transaction once more, partially deliver, clash&crash with a timeout
|
// Notify the dangling transaction once more, partially deliver, clash&crash with a timeout
|
||||||
doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
|
|
||||||
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[1]}, direct: true},
|
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[1]}, direct: true},
|
||||||
doWait{time: txFetchTimeout, step: true},
|
doWait{time: txFetchTimeout, step: true},
|
||||||
|
|
@ -1225,17 +1248,18 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) {
|
||||||
<-proceed
|
<-proceed
|
||||||
return errors.New("peer disconnected")
|
return errors.New("peer disconnected")
|
||||||
},
|
},
|
||||||
|
testTxArrivalWait,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Get a transaction into fetching mode and make it dangling with a broadcast
|
// Get a transaction into fetching mode and make it dangling with a broadcast
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
|
doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
|
||||||
|
|
||||||
// Notify the dangling transaction once more, re-fetch, and crash via an in-flight disconnect
|
// Notify the dangling transaction once more, re-fetch, and crash via an in-flight disconnect
|
||||||
doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
|
doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: testTxArrivalWait, step: true},
|
||||||
doFunc(func() {
|
doFunc(func() {
|
||||||
proceed <- struct{}{} // Allow peer A to return the failure
|
proceed <- struct{}{} // Allow peer A to return the failure
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ type handlerConfig struct {
|
||||||
|
|
||||||
PeerRequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
PeerRequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
||||||
checker ethereum.ChainValidator
|
checker ethereum.ChainValidator
|
||||||
|
txArrivalWait time.Duration // Maximum duration to wait for an announced tx before requesting it
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
|
|
@ -307,7 +308,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
}
|
}
|
||||||
return p.RequestTxs(hashes)
|
return p.RequestTxs(hashes)
|
||||||
}
|
}
|
||||||
h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx)
|
h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx, config.txArrivalWait)
|
||||||
h.chainSync = newChainSyncer(h)
|
h.chainSync = newChainSyncer(h)
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,7 @@ func (p *Peer) dispatcher() {
|
||||||
}
|
}
|
||||||
// Stop tracking the request
|
// Stop tracking the request
|
||||||
delete(pending, cancelOp.id)
|
delete(pending, cancelOp.id)
|
||||||
|
requestTracker.Fulfil(p.id, p.version, req.code, cancelOp.id)
|
||||||
cancelOp.fail <- nil
|
cancelOp.fail <- nil
|
||||||
|
|
||||||
case resOp := <-p.resDispatch:
|
case resOp := <-p.resDispatch:
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,9 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
|
||||||
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
|
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
|
||||||
return nil, fmt.Errorf("block #%d not found", next)
|
return 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
|
return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
|
||||||
}
|
}
|
||||||
|
|
@ -191,9 +193,11 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
|
||||||
// Not yet the searched for transaction, execute on top of the current state
|
// Not yet the searched for transaction, execute on top of the current state
|
||||||
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
|
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
|
||||||
statedb.Prepare(tx.Hash(), idx)
|
statedb.Prepare(tx.Hash(), idx)
|
||||||
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), nil); err != nil {
|
||||||
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure any modifications are committed to the state
|
// Ensure any modifications are committed to the state
|
||||||
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||||
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,13 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -63,10 +66,16 @@ const (
|
||||||
// For non-archive nodes, this limit _will_ be overblown, as disk-backed tries
|
// For non-archive nodes, this limit _will_ be overblown, as disk-backed tries
|
||||||
// will only be found every ~15K blocks or so.
|
// will only be found every ~15K blocks or so.
|
||||||
defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024)
|
defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024)
|
||||||
|
|
||||||
|
defaultPath = string(".")
|
||||||
|
|
||||||
|
defaultIOFlag = false
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultBorTraceEnabled = newBoolPtr(false)
|
var defaultBorTraceEnabled = newBoolPtr(false)
|
||||||
|
|
||||||
|
var allowIOTracing = false // Change this to true to enable IO tracing for debugging
|
||||||
|
|
||||||
// Backend interface provides the common API services (that are provided by
|
// Backend interface provides the common API services (that are provided by
|
||||||
// both full and light clients) with access to necessary functions.
|
// both full and light clients) with access to necessary functions.
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
|
|
@ -196,6 +205,8 @@ type TraceConfig struct {
|
||||||
Tracer *string
|
Tracer *string
|
||||||
Timeout *string
|
Timeout *string
|
||||||
Reexec *uint64
|
Reexec *uint64
|
||||||
|
Path *string
|
||||||
|
IOFlag *bool
|
||||||
BorTraceEnabled *bool
|
BorTraceEnabled *bool
|
||||||
BorTx *bool
|
BorTx *bool
|
||||||
}
|
}
|
||||||
|
|
@ -643,7 +654,8 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
|
// nolint : contextcheck
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background()); err != nil {
|
||||||
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
|
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
|
||||||
// We intentionally don't return the error here: if we do, then the RPC server will not
|
// We intentionally don't return the error here: if we do, then the RPC server will not
|
||||||
// return the roots. Most likely, the caller already knows that a certain transaction fails to
|
// return the roots. Most likely, the caller already knows that a certain transaction fails to
|
||||||
|
|
@ -693,18 +705,37 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
if block.NumberU64() == 0 {
|
if block.NumberU64() == 0 {
|
||||||
return nil, errors.New("genesis is not traceable")
|
return nil, errors.New("genesis is not traceable")
|
||||||
}
|
}
|
||||||
|
|
||||||
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
|
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reexec := defaultTraceReexec
|
reexec := defaultTraceReexec
|
||||||
if config != nil && config.Reexec != nil {
|
if config != nil && config.Reexec != nil {
|
||||||
reexec = *config.Reexec
|
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, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Execute all the transaction contained within the block concurrently
|
||||||
var (
|
var (
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
|
||||||
|
|
@ -755,10 +786,31 @@ 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
|
// Feed the transactions into the tracers and return
|
||||||
var failed error
|
var failed error
|
||||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
|
||||||
|
if ioflag {
|
||||||
|
london = api.backend.ChainConfig().IsLondon(block.Number())
|
||||||
|
}
|
||||||
|
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
|
if ioflag {
|
||||||
|
// copy of statedb
|
||||||
|
statedb = statedb.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
// Send the trace task over for execution
|
// Send the trace task over for execution
|
||||||
jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
|
jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
|
||||||
|
|
||||||
|
|
@ -767,10 +819,14 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
statedb.Prepare(tx.Hash(), i)
|
statedb.Prepare(tx.Hash(), i)
|
||||||
|
|
||||||
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
|
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
|
||||||
|
|
||||||
|
// nolint: nestif
|
||||||
|
if !ioflag {
|
||||||
//nolint: nestif
|
//nolint: nestif
|
||||||
if stateSyncPresent && i == len(txs)-1 {
|
if stateSyncPresent && i == len(txs)-1 {
|
||||||
if *config.BorTraceEnabled {
|
if *config.BorTraceEnabled {
|
||||||
callmsg := prepareCallMessage(msg)
|
callmsg := prepareCallMessage(msg)
|
||||||
|
// nolint : contextcheck
|
||||||
if _, err := statefull.ApplyBorMessage(*vmenv, callmsg); err != nil {
|
if _, err := statefull.ApplyBorMessage(*vmenv, callmsg); err != nil {
|
||||||
failed = err
|
failed = err
|
||||||
break
|
break
|
||||||
|
|
@ -779,16 +835,73 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
|
// nolint : contextcheck
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background()); err != nil {
|
||||||
failed = err
|
failed = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize the state so any modifications are written to the trie
|
// 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
|
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||||
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
coinbaseBalance := statedb.GetBalance(blockCtx.Coinbase)
|
||||||
|
// nolint : contextcheck
|
||||||
|
result, err := core.ApplyMessageNoFeeBurnOrTip(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
failed = err
|
||||||
|
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...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
close(jobs)
|
||||||
pend.Wait()
|
pend.Wait()
|
||||||
|
|
||||||
|
|
@ -926,7 +1039,8 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
|
// nolint : contextcheck
|
||||||
|
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background())
|
||||||
if writer != nil {
|
if writer != nil {
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
@ -1138,7 +1252,8 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||||
return nil, fmt.Errorf("tracing failed: %w", err)
|
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
|
// nolint : contextcheck
|
||||||
|
result, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("tracing failed: %w", err)
|
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ func (api *API) traceBorBlock(ctx context.Context, block *types.Block, config *T
|
||||||
callmsg := prepareCallMessage(message)
|
callmsg := prepareCallMessage(message)
|
||||||
execRes, err = statefull.ApplyBorMessage(*vmenv, callmsg)
|
execRes, err = statefull.ApplyBorMessage(*vmenv, callmsg)
|
||||||
} else {
|
} else {
|
||||||
execRes, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
|
execRes, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -167,14 +167,17 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
|
||||||
for idx, tx := range block.Transactions() {
|
for idx, tx := range block.Transactions() {
|
||||||
msg, _ := tx.AsMessage(signer, block.BaseFee())
|
msg, _ := tx.AsMessage(signer, block.BaseFee())
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
|
blockContext := core.NewEVMBlockContext(block.Header(), b.chain, nil)
|
||||||
if idx == txIndex {
|
if idx == txIndex {
|
||||||
return msg, context, statedb, nil
|
return msg, blockContext, statedb, nil
|
||||||
}
|
}
|
||||||
vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{})
|
|
||||||
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
vmenv := vm.NewEVM(blockContext, txContext, statedb, b.chainConfig, vm.Config{})
|
||||||
|
// nolint : contextcheck
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), context.Background()); err != nil {
|
||||||
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||||
}
|
}
|
||||||
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
|
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
|
||||||
|
|
@ -424,6 +427,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) {
|
func TestTracingWithOverrides(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Initialize test accounts
|
// Initialize test accounts
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package tracetest
|
package tracetest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -168,7 +169,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||||
Origin: origin,
|
Origin: origin,
|
||||||
GasPrice: tx.GasPrice(),
|
GasPrice: tx.GasPrice(),
|
||||||
}
|
}
|
||||||
context = vm.BlockContext{
|
blockContext = vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
Coinbase: test.Context.Miner,
|
Coinbase: test.Context.Miner,
|
||||||
|
|
@ -183,13 +184,13 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
||||||
msg, err := tx.AsMessage(signer, nil)
|
msg, err := tx.AsMessage(signer, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||||
}
|
}
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
if _, err = st.TransitionDb(); err != nil {
|
if _, err = st.TransitionDb(context.Background()); err != nil {
|
||||||
t.Fatalf("failed to execute transaction: %v", err)
|
t.Fatalf("failed to execute transaction: %v", err)
|
||||||
}
|
}
|
||||||
// Retrieve the trace result and compare against the etalon
|
// Retrieve the trace result and compare against the etalon
|
||||||
|
|
@ -279,7 +280,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||||
Origin: origin,
|
Origin: origin,
|
||||||
GasPrice: tx.GasPrice(),
|
GasPrice: tx.GasPrice(),
|
||||||
}
|
}
|
||||||
context := vm.BlockContext{
|
blockContext := vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
Coinbase: test.Context.Miner,
|
Coinbase: test.Context.Miner,
|
||||||
|
|
@ -297,15 +298,19 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed to create call tracer: %v", err)
|
b.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
|
||||||
|
evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
||||||
snap := statedb.Snapshot()
|
snap := statedb.Snapshot()
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
if _, err = st.TransitionDb(); err != nil {
|
|
||||||
|
if _, err = st.TransitionDb(context.Background()); err != nil {
|
||||||
b.Fatalf("failed to execute transaction: %v", err)
|
b.Fatalf("failed to execute transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = tracer.GetResult(); err != nil {
|
if _, err = tracer.GetResult(); err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statedb.RevertToSnapshot(snap)
|
statedb.RevertToSnapshot(snap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -333,7 +338,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
|
||||||
Origin: origin,
|
Origin: origin,
|
||||||
GasPrice: big.NewInt(1),
|
GasPrice: big.NewInt(1),
|
||||||
}
|
}
|
||||||
context := vm.BlockContext{
|
blockContext := vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
Coinbase: common.Address{},
|
Coinbase: common.Address{},
|
||||||
|
|
@ -363,15 +368,18 @@ func TestZeroValueToNotExitCall(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
|
|
||||||
|
evm := vm.NewEVM(blockContext, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||||
msg, err := tx.AsMessage(signer, nil)
|
msg, err := tx.AsMessage(signer, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||||
}
|
}
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
if _, err = st.TransitionDb(); err != nil {
|
|
||||||
|
if _, err = st.TransitionDb(context.Background()); err != nil {
|
||||||
t.Fatalf("failed to execute transaction: %v", err)
|
t.Fatalf("failed to execute transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the trace result and compare against the etalon
|
// Retrieve the trace result and compare against the etalon
|
||||||
res, err := tracer.GetResult()
|
res, err := tracer.GetResult()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
|
||||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
|
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
|
||||||
|
|
||||||
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
|
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
|
||||||
ret, err := env.Interpreter().Run(contract, []byte{}, false)
|
ret, err := env.Interpreter().Run(contract, []byte{}, false, nil)
|
||||||
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
|
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func TestStoreCapture(t *testing.T) {
|
||||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
||||||
var index common.Hash
|
var index common.Hash
|
||||||
logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
|
logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
|
||||||
_, err := env.Interpreter().Run(contract, []byte{}, false)
|
_, err := env.Interpreter().PreRun(contract, []byte{}, false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package tracers
|
package tracers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -66,7 +67,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||||
Origin: from,
|
Origin: from,
|
||||||
GasPrice: tx.GasPrice(),
|
GasPrice: tx.GasPrice(),
|
||||||
}
|
}
|
||||||
context := vm.BlockContext{
|
blockContext := vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
Coinbase: common.Address{},
|
Coinbase: common.Address{},
|
||||||
|
|
@ -102,7 +103,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||||
//EnableMemory: false,
|
//EnableMemory: false,
|
||||||
//EnableReturnData: false,
|
//EnableReturnData: false,
|
||||||
})
|
})
|
||||||
evm := vm.NewEVM(context, txContext, statedb, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer})
|
evm := vm.NewEVM(blockContext, txContext, statedb, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer})
|
||||||
msg, err := tx.AsMessage(signer, nil)
|
msg, err := tx.AsMessage(signer, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed to prepare transaction for tracing: %v", err)
|
b.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||||
|
|
@ -113,7 +114,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
snap := statedb.Snapshot()
|
snap := statedb.Snapshot()
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
_, err = st.TransitionDb()
|
_, err = st.TransitionDb(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -38,6 +38,7 @@ require (
|
||||||
github.com/hashicorp/go-bexpr v0.1.10
|
github.com/hashicorp/go-bexpr v0.1.10
|
||||||
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
|
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
|
||||||
github.com/hashicorp/hcl/v2 v2.10.1
|
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/big v0.0.0-20221017200358-a027dc42d04e
|
||||||
github.com/holiman/bloomfilter/v2 v2.0.3
|
github.com/holiman/bloomfilter/v2 v2.0.3
|
||||||
github.com/holiman/uint256 v1.2.0
|
github.com/holiman/uint256 v1.2.0
|
||||||
|
|
@ -131,6 +132,7 @@ require (
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||||
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // 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/etcd-io/bbolt v1.3.3 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/go-kit/kit v0.10.0 // indirect
|
github.com/go-kit/kit v0.10.0 // indirect
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -254,6 +254,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
|
||||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
|
@ -324,6 +326,8 @@ github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||||
|
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||||
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
||||||
|
|
@ -498,6 +502,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/heimdalr/dag v1.2.1 h1:XJOMaoWqJK1UKdp+4zaO2uwav9GFbHMGCirdViKMRIQ=
|
||||||
|
github.com/heimdalr/dag v1.2.1/go.mod h1:Of/wUB7Yoj4dwiOcGOOYIq6MHlPF/8/QMBKFJpwg+yc=
|
||||||
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw=
|
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw=
|
||||||
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e/go.mod h1:j9cQbcqHQujT0oKJ38PylVfqohClLr3CvDC+Qcg+lhU=
|
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e/go.mod h1:j9cQbcqHQujT0oKJ38PylVfqohClLr3CvDC+Qcg+lhU=
|
||||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||||
|
|
|
||||||
|
|
@ -1006,7 +1006,8 @@ func (b *Block) Call(ctx context.Context, args struct {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, b.backend.RPCEVMTimeout(), b.backend.RPCGasCap())
|
|
||||||
|
result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, nil, b.backend.RPCEVMTimeout(), b.backend.RPCGasCap())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1076,7 +1077,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
|
||||||
Data ethapi.TransactionArgs
|
Data ethapi.TransactionArgs
|
||||||
}) (*CallResult, error) {
|
}) (*CallResult, error) {
|
||||||
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
|
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
|
||||||
result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, p.backend.RPCEVMTimeout(), p.backend.RPCGasCap())
|
result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, nil, p.backend.RPCEVMTimeout(), p.backend.RPCGasCap())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ func (c *DumpconfigCommand) Run(args []string) int {
|
||||||
userConfig.Gpo.IgnorePriceRaw = userConfig.Gpo.IgnorePrice.String()
|
userConfig.Gpo.IgnorePriceRaw = userConfig.Gpo.IgnorePrice.String()
|
||||||
userConfig.Cache.RejournalRaw = userConfig.Cache.Rejournal.String()
|
userConfig.Cache.RejournalRaw = userConfig.Cache.Rejournal.String()
|
||||||
userConfig.Cache.TrieTimeoutRaw = userConfig.Cache.TrieTimeout.String()
|
userConfig.Cache.TrieTimeoutRaw = userConfig.Cache.TrieTimeout.String()
|
||||||
|
userConfig.P2P.TxArrivalWaitRaw = userConfig.P2P.TxArrivalWait.String()
|
||||||
|
|
||||||
if err := toml.NewEncoder(os.Stdout).Encode(userConfig); err != nil {
|
if err := toml.NewEncoder(os.Stdout).Encode(userConfig); err != nil {
|
||||||
c.UI.Error(err.Error())
|
c.UI.Error(err.Error())
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ var mainnetBor = &Chain{
|
||||||
Bor: ¶ms.BorConfig{
|
Bor: ¶ms.BorConfig{
|
||||||
JaipurBlock: big.NewInt(23850000),
|
JaipurBlock: big.NewInt(23850000),
|
||||||
DelhiBlock: big.NewInt(38189056),
|
DelhiBlock: big.NewInt(38189056),
|
||||||
|
ParallelUniverseBlock: big.NewInt(0),
|
||||||
|
IndoreBlock: big.NewInt(44934656),
|
||||||
|
StateSyncConfirmationDelay: map[string]uint64{
|
||||||
|
"44934656": 128,
|
||||||
|
},
|
||||||
Period: map[string]uint64{
|
Period: map[string]uint64{
|
||||||
"0": 2,
|
"0": 2,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ var mumbaiTestnet = &Chain{
|
||||||
Bor: ¶ms.BorConfig{
|
Bor: ¶ms.BorConfig{
|
||||||
JaipurBlock: big.NewInt(22770000),
|
JaipurBlock: big.NewInt(22770000),
|
||||||
DelhiBlock: big.NewInt(29638656),
|
DelhiBlock: big.NewInt(29638656),
|
||||||
|
ParallelUniverseBlock: big.NewInt(0),
|
||||||
|
IndoreBlock: big.NewInt(37075456),
|
||||||
|
StateSyncConfirmationDelay: map[string]uint64{
|
||||||
|
"37075456": 128,
|
||||||
|
},
|
||||||
Period: map[string]uint64{
|
Period: map[string]uint64{
|
||||||
"0": 2,
|
"0": 2,
|
||||||
"25275000": 5,
|
"25275000": 5,
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,12 @@
|
||||||
"22640000": "0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
|
"22640000": "0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
|
||||||
},
|
},
|
||||||
"jaipurBlock": 22770000,
|
"jaipurBlock": 22770000,
|
||||||
"delhiBlock": 29638656
|
"delhiBlock": 29638656,
|
||||||
|
"parallelUniverseBlock": 0,
|
||||||
|
"indoreBlock": 37075456,
|
||||||
|
"stateSyncConfirmationDelay": {
|
||||||
|
"37075456": 128
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nonce": "0x0",
|
"nonce": "0x0",
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,12 @@
|
||||||
"22640000":"0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
|
"22640000":"0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
|
||||||
},
|
},
|
||||||
"jaipurBlock":22770000,
|
"jaipurBlock":22770000,
|
||||||
"delhiBlock": 29638656
|
"delhiBlock": 29638656,
|
||||||
|
"parallelUniverseBlock": 0,
|
||||||
|
"indoreBlock": 37075456,
|
||||||
|
"stateSyncConfirmationDelay": {
|
||||||
|
"37075456": 128
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nonce":"0x0",
|
"nonce":"0x0",
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,9 @@ type Config struct {
|
||||||
// Developer has the developer mode related settings
|
// Developer has the developer mode related settings
|
||||||
Developer *DeveloperConfig `hcl:"developer,block" toml:"developer,block"`
|
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
|
// Develop Fake Author mode to produce blocks without authorisation
|
||||||
DevFakeAuthor bool `hcl:"devfakeauthor,optional" toml:"devfakeauthor,optional"`
|
DevFakeAuthor bool `hcl:"devfakeauthor,optional" toml:"devfakeauthor,optional"`
|
||||||
|
|
||||||
|
|
@ -204,6 +207,11 @@ type P2PConfig struct {
|
||||||
|
|
||||||
// Discovery has the p2p discovery related settings
|
// Discovery has the p2p discovery related settings
|
||||||
Discovery *P2PDiscovery `hcl:"discovery,block" toml:"discovery,block"`
|
Discovery *P2PDiscovery `hcl:"discovery,block" toml:"discovery,block"`
|
||||||
|
|
||||||
|
// TxArrivalWait sets the maximum duration the transaction fetcher will wait for
|
||||||
|
// an announced transaction to arrive before explicitly requesting it
|
||||||
|
TxArrivalWait time.Duration `hcl:"-,optional" toml:"-"`
|
||||||
|
TxArrivalWaitRaw string `hcl:"txarrivalwait,optional" toml:"txarrivalwait,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type P2PDiscovery struct {
|
type P2PDiscovery struct {
|
||||||
|
|
@ -306,6 +314,8 @@ type SealerConfig struct {
|
||||||
// The time interval for miner to re-create mining work.
|
// The time interval for miner to re-create mining work.
|
||||||
Recommit time.Duration `hcl:"-,optional" toml:"-"`
|
Recommit time.Duration `hcl:"-,optional" toml:"-"`
|
||||||
RecommitRaw string `hcl:"recommit,optional" toml:"recommit,optional"`
|
RecommitRaw string `hcl:"recommit,optional" toml:"recommit,optional"`
|
||||||
|
|
||||||
|
CommitInterruptFlag bool `hcl:"commitinterrupt,optional" toml:"commitinterrupt,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JsonRPCConfig struct {
|
type JsonRPCConfig struct {
|
||||||
|
|
@ -563,6 +573,12 @@ type DeveloperConfig struct {
|
||||||
GasLimit uint64 `hcl:"gaslimit,optional" toml:"gaslimit,optional"`
|
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 {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Chain: "mainnet",
|
Chain: "mainnet",
|
||||||
|
|
@ -589,6 +605,7 @@ func DefaultConfig() *Config {
|
||||||
NoDiscover: false,
|
NoDiscover: false,
|
||||||
NAT: "any",
|
NAT: "any",
|
||||||
NetRestrict: "",
|
NetRestrict: "",
|
||||||
|
TxArrivalWait: 500 * time.Millisecond,
|
||||||
Discovery: &P2PDiscovery{
|
Discovery: &P2PDiscovery{
|
||||||
V5Enabled: false,
|
V5Enabled: false,
|
||||||
Bootnodes: []string{},
|
Bootnodes: []string{},
|
||||||
|
|
@ -628,6 +645,7 @@ func DefaultConfig() *Config {
|
||||||
GasPrice: big.NewInt(1 * params.GWei), // geth's default
|
GasPrice: big.NewInt(1 * params.GWei), // geth's default
|
||||||
ExtraData: "",
|
ExtraData: "",
|
||||||
Recommit: 125 * time.Second,
|
Recommit: 125 * time.Second,
|
||||||
|
CommitInterruptFlag: true,
|
||||||
},
|
},
|
||||||
Gpo: &GpoConfig{
|
Gpo: &GpoConfig{
|
||||||
Blocks: 20,
|
Blocks: 20,
|
||||||
|
|
@ -687,7 +705,7 @@ func DefaultConfig() *Config {
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Expensive: false,
|
Expensive: false,
|
||||||
PrometheusAddr: "127.0.0.1:7071",
|
PrometheusAddr: "127.0.0.1:7071",
|
||||||
OpenCollectorEndpoint: "127.0.0.1:4317",
|
OpenCollectorEndpoint: "",
|
||||||
InfluxDB: &InfluxDBConfig{
|
InfluxDB: &InfluxDBConfig{
|
||||||
V1Enabled: false,
|
V1Enabled: false,
|
||||||
Endpoint: "",
|
Endpoint: "",
|
||||||
|
|
@ -740,6 +758,10 @@ func DefaultConfig() *Config {
|
||||||
BlockProfileRate: 0,
|
BlockProfileRate: 0,
|
||||||
// CPUProfile: "",
|
// CPUProfile: "",
|
||||||
},
|
},
|
||||||
|
ParallelEVM: &ParallelEVMConfig{
|
||||||
|
Enable: true,
|
||||||
|
SpeculativeProcesses: 8,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -795,6 +817,7 @@ func (c *Config) fillTimeDurations() error {
|
||||||
{"txpool.rejournal", &c.TxPool.Rejournal, &c.TxPool.RejournalRaw},
|
{"txpool.rejournal", &c.TxPool.Rejournal, &c.TxPool.RejournalRaw},
|
||||||
{"cache.rejournal", &c.Cache.Rejournal, &c.Cache.RejournalRaw},
|
{"cache.rejournal", &c.Cache.Rejournal, &c.Cache.RejournalRaw},
|
||||||
{"cache.timeout", &c.Cache.TrieTimeout, &c.Cache.TrieTimeoutRaw},
|
{"cache.timeout", &c.Cache.TrieTimeout, &c.Cache.TrieTimeoutRaw},
|
||||||
|
{"p2p.txarrivalwait", &c.P2P.TxArrivalWait, &c.P2P.TxArrivalWaitRaw},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range tds {
|
for _, x := range tds {
|
||||||
|
|
@ -916,6 +939,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (*
|
||||||
n.Miner.GasPrice = c.Sealer.GasPrice
|
n.Miner.GasPrice = c.Sealer.GasPrice
|
||||||
n.Miner.GasCeil = c.Sealer.GasCeil
|
n.Miner.GasCeil = c.Sealer.GasCeil
|
||||||
n.Miner.ExtraData = []byte(c.Sealer.ExtraData)
|
n.Miner.ExtraData = []byte(c.Sealer.ExtraData)
|
||||||
|
n.Miner.CommitInterruptFlag = c.Sealer.CommitInterruptFlag
|
||||||
|
|
||||||
if etherbase := c.Sealer.Etherbase; etherbase != "" {
|
if etherbase := c.Sealer.Etherbase; etherbase != "" {
|
||||||
if !common.IsHexAddress(etherbase) {
|
if !common.IsHexAddress(etherbase) {
|
||||||
|
|
@ -1121,6 +1145,8 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (*
|
||||||
n.BorLogs = c.BorLogs
|
n.BorLogs = c.BorLogs
|
||||||
n.DatabaseHandles = dbHandles
|
n.DatabaseHandles = dbHandles
|
||||||
|
|
||||||
|
n.ParallelEVM.Enable = c.ParallelEVM.Enable
|
||||||
|
n.ParallelEVM.SpeculativeProcesses = c.ParallelEVM.SpeculativeProcesses
|
||||||
n.RPCReturnDataLimit = c.RPCReturnDataLimit
|
n.RPCReturnDataLimit = c.RPCReturnDataLimit
|
||||||
|
|
||||||
if c.Ancient != "" {
|
if c.Ancient != "" {
|
||||||
|
|
@ -1257,6 +1283,7 @@ func (c *Config) buildNode() (*node.Config, error) {
|
||||||
MaxPendingPeers: int(c.P2P.MaxPendPeers),
|
MaxPendingPeers: int(c.P2P.MaxPendPeers),
|
||||||
ListenAddr: c.P2P.Bind + ":" + strconv.Itoa(int(c.P2P.Port)),
|
ListenAddr: c.P2P.Bind + ":" + strconv.Itoa(int(c.P2P.Port)),
|
||||||
DiscoveryV5: c.P2P.Discovery.V5Enabled,
|
DiscoveryV5: c.P2P.Discovery.V5Enabled,
|
||||||
|
TxArrivalWait: c.P2P.TxArrivalWait,
|
||||||
},
|
},
|
||||||
HTTPModules: c.JsonRPC.Http.API,
|
HTTPModules: c.JsonRPC.Http.API,
|
||||||
HTTPCors: c.JsonRPC.Http.Cors,
|
HTTPCors: c.JsonRPC.Http.Cors,
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,13 @@ func (c *Command) Flags() *flagset.Flagset {
|
||||||
Default: c.cliConfig.Sealer.Recommit,
|
Default: c.cliConfig.Sealer.Recommit,
|
||||||
Group: "Sealer",
|
Group: "Sealer",
|
||||||
})
|
})
|
||||||
|
f.BoolFlag(&flagset.BoolFlag{
|
||||||
|
Name: "miner.interruptcommit",
|
||||||
|
Usage: "Interrupt block commit when block creation time is passed",
|
||||||
|
Value: &c.cliConfig.Sealer.CommitInterruptFlag,
|
||||||
|
Default: c.cliConfig.Sealer.CommitInterruptFlag,
|
||||||
|
Group: "Sealer",
|
||||||
|
})
|
||||||
|
|
||||||
// ethstats
|
// ethstats
|
||||||
f.StringFlag(&flagset.StringFlag{
|
f.StringFlag(&flagset.StringFlag{
|
||||||
|
|
@ -731,6 +738,13 @@ func (c *Command) Flags() *flagset.Flagset {
|
||||||
Default: c.cliConfig.P2P.Discovery.V5Enabled,
|
Default: c.cliConfig.P2P.Discovery.V5Enabled,
|
||||||
Group: "P2P",
|
Group: "P2P",
|
||||||
})
|
})
|
||||||
|
f.DurationFlag(&flagset.DurationFlag{
|
||||||
|
Name: "txarrivalwait",
|
||||||
|
Usage: "Maximum duration to wait for a transaction before explicitly requesting it (defaults to 500ms)",
|
||||||
|
Value: &c.cliConfig.P2P.TxArrivalWait,
|
||||||
|
Default: c.cliConfig.P2P.TxArrivalWait,
|
||||||
|
Group: "P2P",
|
||||||
|
})
|
||||||
|
|
||||||
// metrics
|
// metrics
|
||||||
f.BoolFlag(&flagset.BoolFlag{
|
f.BoolFlag(&flagset.BoolFlag{
|
||||||
|
|
@ -890,6 +904,20 @@ func (c *Command) Flags() *flagset.Flagset {
|
||||||
Value: &c.cliConfig.Developer.Period,
|
Value: &c.cliConfig.Developer.Period,
|
||||||
Default: 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{
|
f.Uint64Flag(&flagset.Uint64Flag{
|
||||||
Name: "dev.gaslimit",
|
Name: "dev.gaslimit",
|
||||||
Usage: "Initial block gas limit",
|
Usage: "Initial block gas limit",
|
||||||
|
|
|
||||||
|
|
@ -987,16 +987,40 @@ func (diff *StateOverride) Apply(state *state.StateDB) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
|
||||||
|
|
||||||
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
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 {
|
if state == nil || err != nil {
|
||||||
return nil, err
|
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 {
|
if err := overrides.Apply(state); err != nil {
|
||||||
return nil, err
|
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
|
// Setup context so it may be cancelled the call has completed
|
||||||
// or, in case of unmetered gas, setup a context with a timeout.
|
// or, in case of unmetered gas, setup a context with a timeout.
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
|
|
@ -1027,7 +1051,8 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
|
||||||
|
|
||||||
// Execute the message.
|
// Execute the message.
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
result, err := core.ApplyMessage(evm, msg, gp)
|
// nolint : contextcheck
|
||||||
|
result, err := core.ApplyMessage(evm, msg, gp, context.Background())
|
||||||
if err := vmError(); err != nil {
|
if err := vmError(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1079,7 +1104,20 @@ func (e *revertError) ErrorData() interface{} {
|
||||||
// Note, this function doesn't make and changes in the state/blockchain and is
|
// Note, this function doesn't make and changes in the state/blockchain and is
|
||||||
// useful to execute and retrieve values.
|
// useful to execute and retrieve values.
|
||||||
func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
|
func (s *PublicBlockChainAPI) 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 *PublicBlockChainAPI) 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1092,6 +1130,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl
|
||||||
if len(result.Revert()) > 0 {
|
if len(result.Revert()) > 0 {
|
||||||
return nil, newRevertError(result)
|
return nil, newRevertError(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Return(), result.Err
|
return result.Return(), result.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1169,7 +1208,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
|
||||||
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
||||||
args.Gas = (*hexutil.Uint64)(&gas)
|
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 err != nil {
|
||||||
if errors.Is(err, core.ErrIntrinsicGas) {
|
if errors.Is(err, core.ErrIntrinsicGas) {
|
||||||
return true, nil, nil // Special case, raise gas limit
|
return true, nil, nil // Special case, raise gas limit
|
||||||
|
|
@ -1595,13 +1634,16 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
|
// nolint : contextcheck
|
||||||
|
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
|
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tracer.Equal(prevTracer) {
|
if tracer.Equal(prevTracer) {
|
||||||
return accessList, res.UsedGas, res.Err, nil
|
return accessList, res.UsedGas, res.Err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
prevTracer = tracer
|
prevTracer = tracer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,13 +137,14 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
|
||||||
|
|
||||||
msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
|
msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
|
||||||
|
|
||||||
context := core.NewEVMBlockContext(header, bc, nil)
|
blockContext := core.NewEVMBlockContext(header, bc, nil)
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{NoBaseFee: true})
|
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, vm.Config{NoBaseFee: true})
|
||||||
|
|
||||||
//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
|
//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
result, _ := core.ApplyMessage(vmenv, msg, gp)
|
// nolint : contextcheck
|
||||||
|
result, _ := core.ApplyMessage(vmenv, msg, gp, context.Background())
|
||||||
res = append(res, result.Return()...)
|
res = append(res, result.Return()...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -151,11 +152,12 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
|
||||||
state := light.NewState(ctx, header, lc.Odr())
|
state := light.NewState(ctx, header, lc.Odr())
|
||||||
state.SetBalance(bankAddr, math.MaxBig256)
|
state.SetBalance(bankAddr, math.MaxBig256)
|
||||||
msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
|
msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
|
||||||
context := core.NewEVMBlockContext(header, lc, nil)
|
blockContext := core.NewEVMBlockContext(header, lc, nil)
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true})
|
vmenv := vm.NewEVM(blockContext, txContext, state, config, vm.Config{NoBaseFee: true})
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
result, _ := core.ApplyMessage(vmenv, msg, gp)
|
// nolint : contextcheck
|
||||||
|
result, _ := core.ApplyMessage(vmenv, msg, gp, context.Background())
|
||||||
if state.Error() == nil {
|
if state.Error() == nil {
|
||||||
res = append(res, result.Return()...)
|
res = append(res, result.Return()...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,14 +57,15 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.
|
||||||
// Assemble the transaction call message and return if the requested offset
|
// Assemble the transaction call message and return if the requested offset
|
||||||
msg, _ := tx.AsMessage(signer, block.BaseFee())
|
msg, _ := tx.AsMessage(signer, block.BaseFee())
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil)
|
blockContext := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil)
|
||||||
statedb.Prepare(tx.Hash(), idx)
|
statedb.Prepare(tx.Hash(), idx)
|
||||||
if idx == txIndex {
|
if idx == txIndex {
|
||||||
return msg, context, statedb, nil
|
return msg, blockContext, statedb, nil
|
||||||
}
|
}
|
||||||
// Not yet the searched for transaction, execute on top of the current state
|
// Not yet the searched for transaction, execute on top of the current state
|
||||||
vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{})
|
vmenv := vm.NewEVM(blockContext, txContext, statedb, leth.blockchain.Config(), vm.Config{})
|
||||||
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
// nolint : contextcheck
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), context.Background()); err != nil {
|
||||||
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||||
}
|
}
|
||||||
// Ensure any modifications are committed to the state
|
// Ensure any modifications are committed to the state
|
||||||
|
|
|
||||||
|
|
@ -196,10 +196,11 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
|
||||||
st.SetBalance(testBankAddress, math.MaxBig256)
|
st.SetBalance(testBankAddress, math.MaxBig256)
|
||||||
msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
|
msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, big.NewInt(params.InitialBaseFee), big.NewInt(params.InitialBaseFee), new(big.Int), data, nil, true)}
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(header, chain, nil)
|
blockContext := core.NewEVMBlockContext(header, chain, nil)
|
||||||
vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
|
vmenv := vm.NewEVM(blockContext, txContext, st, config, vm.Config{NoBaseFee: true})
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
result, _ := core.ApplyMessage(vmenv, msg, gp)
|
// nolint : contextcheck
|
||||||
|
result, _ := core.ApplyMessage(vmenv, msg, gp, context.Background())
|
||||||
res = append(res, result.Return()...)
|
res = append(res, result.Return()...)
|
||||||
if st.Error() != nil {
|
if st.Error() != nil {
|
||||||
return res, st.Error()
|
return res, st.Error()
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ const skipLevel = 2
|
||||||
type Lvl int
|
type Lvl int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LvlDiscard Lvl = -1
|
|
||||||
LvlCrit Lvl = iota
|
LvlCrit Lvl = iota
|
||||||
LvlError
|
LvlError
|
||||||
LvlWarn
|
LvlWarn
|
||||||
LvlInfo
|
LvlInfo
|
||||||
LvlDebug
|
LvlDebug
|
||||||
LvlTrace
|
LvlTrace
|
||||||
|
LvlDiscard Lvl = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
// AlignedString returns a 5-character string containing the name of a Lvl.
|
// AlignedString returns a 5-character string containing the name of a Lvl.
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
typeGaugeTpl = "# TYPE %s gauge\n"
|
typeGaugeTpl = "# TYPE %s gauge\n"
|
||||||
typeCounterTpl = "# TYPE %s counter\n"
|
|
||||||
typeSummaryTpl = "# TYPE %s summary\n"
|
typeSummaryTpl = "# TYPE %s summary\n"
|
||||||
keyValueTpl = "%s %v\n\n"
|
keyValueTpl = "%s %v\n\n"
|
||||||
|
keyCounterTpl = "%s %v\n"
|
||||||
keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n"
|
keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -61,11 +61,16 @@ func (c *collector) addGaugeFloat64(name string, m metrics.GaugeFloat64) {
|
||||||
func (c *collector) addHistogram(name string, m metrics.Histogram) {
|
func (c *collector) addHistogram(name string, m metrics.Histogram) {
|
||||||
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
|
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
|
||||||
ps := m.Percentiles(pv)
|
ps := m.Percentiles(pv)
|
||||||
c.writeSummaryCounter(name, m.Count())
|
|
||||||
|
var sum float64 = 0
|
||||||
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
||||||
for i := range pv {
|
for i := range pv {
|
||||||
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
|
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')
|
c.buff.WriteRune('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,11 +81,16 @@ func (c *collector) addMeter(name string, m metrics.Meter) {
|
||||||
func (c *collector) addTimer(name string, m metrics.Timer) {
|
func (c *collector) addTimer(name string, m metrics.Timer) {
|
||||||
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
|
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
|
||||||
ps := m.Percentiles(pv)
|
ps := m.Percentiles(pv)
|
||||||
c.writeSummaryCounter(name, m.Count())
|
|
||||||
|
var sum float64 = 0
|
||||||
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
||||||
for i := range pv {
|
for i := range pv {
|
||||||
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
|
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')
|
c.buff.WriteRune('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,11 +100,19 @@ func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) {
|
||||||
}
|
}
|
||||||
ps := m.Percentiles([]float64{50, 95, 99})
|
ps := m.Percentiles([]float64{50, 95, 99})
|
||||||
val := m.Values()
|
val := m.Values()
|
||||||
c.writeSummaryCounter(name, len(val))
|
|
||||||
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
||||||
c.writeSummaryPercentile(name, "0.50", ps[0])
|
c.writeSummaryPercentile(name, "0.50", ps[0])
|
||||||
c.writeSummaryPercentile(name, "0.95", ps[1])
|
c.writeSummaryPercentile(name, "0.95", ps[1])
|
||||||
c.writeSummaryPercentile(name, "0.99", ps[2])
|
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')
|
c.buff.WriteRune('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,8 +124,7 @@ func (c *collector) writeGaugeCounter(name string, value interface{}) {
|
||||||
|
|
||||||
func (c *collector) writeSummaryCounter(name string, value interface{}) {
|
func (c *collector) writeSummaryCounter(name string, value interface{}) {
|
||||||
name = mutateKey(name + "_count")
|
name = mutateKey(name + "_count")
|
||||||
c.buff.WriteString(fmt.Sprintf(typeCounterTpl, name))
|
c.buff.WriteString(fmt.Sprintf(keyCounterTpl, name, value))
|
||||||
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
|
func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
|
||||||
|
|
@ -115,6 +132,11 @@ func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
|
||||||
c.buff.WriteString(fmt.Sprintf(keyQuantileTagValueTpl, name, p, value))
|
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 {
|
func mutateKey(key string) string {
|
||||||
return strings.Replace(key, "/", "_", -1)
|
return strings.Replace(key, "/", "_", -1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,6 @@ test_gauge 23456
|
||||||
# TYPE test_gauge_float64 gauge
|
# TYPE test_gauge_float64 gauge
|
||||||
test_gauge_float64 34567.89
|
test_gauge_float64 34567.89
|
||||||
|
|
||||||
# TYPE test_histogram_count counter
|
|
||||||
test_histogram_count 0
|
|
||||||
|
|
||||||
# TYPE test_histogram summary
|
# TYPE test_histogram summary
|
||||||
test_histogram {quantile="0.5"} 0
|
test_histogram {quantile="0.5"} 0
|
||||||
test_histogram {quantile="0.75"} 0
|
test_histogram {quantile="0.75"} 0
|
||||||
|
|
@ -77,13 +74,12 @@ test_histogram {quantile="0.95"} 0
|
||||||
test_histogram {quantile="0.99"} 0
|
test_histogram {quantile="0.99"} 0
|
||||||
test_histogram {quantile="0.999"} 0
|
test_histogram {quantile="0.999"} 0
|
||||||
test_histogram {quantile="0.9999"} 0
|
test_histogram {quantile="0.9999"} 0
|
||||||
|
test_histogram_sum 0.000000
|
||||||
|
test_histogram_count 6
|
||||||
|
|
||||||
# TYPE test_meter gauge
|
# TYPE test_meter gauge
|
||||||
test_meter 9999999
|
test_meter 9999999
|
||||||
|
|
||||||
# TYPE test_timer_count counter
|
|
||||||
test_timer_count 6
|
|
||||||
|
|
||||||
# TYPE test_timer summary
|
# TYPE test_timer summary
|
||||||
test_timer {quantile="0.5"} 2.25e+07
|
test_timer {quantile="0.5"} 2.25e+07
|
||||||
test_timer {quantile="0.75"} 4.8e+07
|
test_timer {quantile="0.75"} 4.8e+07
|
||||||
|
|
@ -91,16 +87,20 @@ test_timer {quantile="0.95"} 1.2e+08
|
||||||
test_timer {quantile="0.99"} 1.2e+08
|
test_timer {quantile="0.99"} 1.2e+08
|
||||||
test_timer {quantile="0.999"} 1.2e+08
|
test_timer {quantile="0.999"} 1.2e+08
|
||||||
test_timer {quantile="0.9999"} 1.2e+08
|
test_timer {quantile="0.9999"} 1.2e+08
|
||||||
|
test_timer_sum 550500000.000000
|
||||||
# TYPE test_resetting_timer_count counter
|
test_timer_count 6
|
||||||
test_resetting_timer_count 6
|
|
||||||
|
|
||||||
# TYPE test_resetting_timer summary
|
# TYPE test_resetting_timer summary
|
||||||
test_resetting_timer {quantile="0.50"} 12000000
|
test_resetting_timer {quantile="0.50"} 12000000
|
||||||
test_resetting_timer {quantile="0.95"} 120000000
|
test_resetting_timer {quantile="0.95"} 120000000
|
||||||
test_resetting_timer {quantile="0.99"} 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()
|
exp := c.buff.String()
|
||||||
if exp != expectedOutput {
|
if exp != expectedOutput {
|
||||||
t.Log("Expected Output:\n", expectedOutput)
|
t.Log("Expected Output:\n", expectedOutput)
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,7 @@ var (
|
||||||
testConfig = &Config{
|
testConfig = &Config{
|
||||||
Recommit: time.Second,
|
Recommit: time.Second,
|
||||||
GasCeil: params.GenesisGasLimit,
|
GasCeil: params.GenesisGasLimit,
|
||||||
|
CommitInterruptFlag: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ type Config struct {
|
||||||
GasPrice *big.Int // Minimum gas price for mining a transaction
|
GasPrice *big.Int // Minimum gas price for mining a transaction
|
||||||
Recommit time.Duration // The time interval for miner to re-create mining work.
|
Recommit time.Duration // The time interval for miner to re-create mining work.
|
||||||
Noverify bool // Disable remote mining solution verification(only useful in ethash).
|
Noverify bool // Disable remote mining solution verification(only useful in ethash).
|
||||||
|
CommitInterruptFlag bool // Interrupt commit when time is up ( default = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Miner creates blocks and searches for proof-of-work values.
|
// Miner creates blocks and searches for proof-of-work values.
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ import (
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -40,6 +42,10 @@ const (
|
||||||
|
|
||||||
// testGas is the gas required for contract deployment.
|
// testGas is the gas required for contract deployment.
|
||||||
testGas = 144109
|
testGas = 144109
|
||||||
|
|
||||||
|
storageContractByteCode = "608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea2646970667358221220322c78243e61b783558509c9cc22cb8493dde6925aa5e89a08cdf6e22f279ef164736f6c63430008120033"
|
||||||
|
storageContractTxCallData = "0x6057361d0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
storageCallTxGas = 100000
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -167,6 +173,7 @@ func (b *testWorkerBackend) newRandomUncle() (*types.Block, error) {
|
||||||
return blocks[0], err
|
return blocks[0], err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newRandomTx creates a new transaction.
|
||||||
func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
|
func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
|
||||||
var tx *types.Transaction
|
var tx *types.Transaction
|
||||||
|
|
||||||
|
|
@ -181,15 +188,54 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestWorker(t TensingObject, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, noempty uint32, delay uint) (*worker, *testWorkerBackend, func()) {
|
// newRandomTxWithNonce creates a new transaction with the given nonce.
|
||||||
|
func (b *testWorkerBackend) newRandomTxWithNonce(creation bool, nonce uint64) *types.Transaction {
|
||||||
|
var tx *types.Transaction
|
||||||
|
|
||||||
|
gasPrice := big.NewInt(100 * params.InitialBaseFee)
|
||||||
|
|
||||||
|
if creation {
|
||||||
|
tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(TestBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey)
|
||||||
|
} else {
|
||||||
|
tx, _ = types.SignTx(types.NewTransaction(nonce, testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRandomTxWithGas creates a new transaction to deploy a storage smart contract.
|
||||||
|
func (b *testWorkerBackend) newStorageCreateContractTx() (*types.Transaction, common.Address) {
|
||||||
|
var tx *types.Transaction
|
||||||
|
|
||||||
|
gasPrice := big.NewInt(10 * params.InitialBaseFee)
|
||||||
|
|
||||||
|
tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(TestBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(storageContractByteCode)), types.HomesteadSigner{}, testBankKey)
|
||||||
|
contractAddr := crypto.CreateAddress(TestBankAddress, b.txPool.Nonce(TestBankAddress))
|
||||||
|
|
||||||
|
return tx, contractAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStorageContractCallTx creates a new transaction to call a storage smart contract.
|
||||||
|
func (b *testWorkerBackend) newStorageContractCallTx(to common.Address, nonce uint64) *types.Transaction {
|
||||||
|
var tx *types.Transaction
|
||||||
|
|
||||||
|
gasPrice := big.NewInt(10 * params.InitialBaseFee)
|
||||||
|
|
||||||
|
tx, _ = types.SignTx(types.NewTransaction(nonce, to, nil, storageCallTxGas, gasPrice, common.FromHex(storageContractTxCallData)), types.HomesteadSigner{}, testBankKey)
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestWorker creates a new test worker with the given parameters.
|
||||||
|
func NewTestWorker(t TensingObject, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, noempty uint32, delay uint, opcodeDelay uint) (*worker, *testWorkerBackend, func()) {
|
||||||
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
|
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
|
||||||
backend.txPool.AddLocals(pendingTxs)
|
backend.txPool.AddLocals(pendingTxs)
|
||||||
|
|
||||||
var w *worker
|
var w *worker
|
||||||
|
|
||||||
if delay != 0 {
|
if delay != 0 || opcodeDelay != 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
w = newWorkerWithDelay(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, delay)
|
w = newWorkerWithDelay(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, delay, opcodeDelay)
|
||||||
} else {
|
} else {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
w = newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false)
|
w = newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false)
|
||||||
|
|
@ -203,8 +249,9 @@ func NewTestWorker(t TensingObject, chainConfig *params.ChainConfig, engine cons
|
||||||
return w, backend, w.close
|
return w, backend, w.close
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:staticcheck
|
// newWorkerWithDelay is newWorker() with extra params to induce artficial delays for tests such as commit-interrupt.
|
||||||
func newWorkerWithDelay(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool, delay uint) *worker {
|
// nolint:staticcheck
|
||||||
|
func newWorkerWithDelay(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool, delay uint, opcodeDelay uint) *worker {
|
||||||
worker := &worker{
|
worker := &worker{
|
||||||
config: config,
|
config: config,
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
|
|
@ -229,6 +276,7 @@ func newWorkerWithDelay(config *Config, chainConfig *params.ChainConfig, engine
|
||||||
resubmitIntervalCh: make(chan time.Duration),
|
resubmitIntervalCh: make(chan time.Duration),
|
||||||
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
|
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
|
||||||
noempty: 1,
|
noempty: 1,
|
||||||
|
interruptCommitFlag: config.CommitInterruptFlag,
|
||||||
}
|
}
|
||||||
worker.profileCount = new(int32)
|
worker.profileCount = new(int32)
|
||||||
// Subscribe NewTxsEvent for tx pool
|
// Subscribe NewTxsEvent for tx pool
|
||||||
|
|
@ -237,6 +285,19 @@ func newWorkerWithDelay(config *Config, chainConfig *params.ChainConfig, engine
|
||||||
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
|
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
|
||||||
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
|
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
|
||||||
|
|
||||||
|
interruptedTxCache, err := lru.New(vm.InterruptedTxCacheSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Failed to create interrupted tx cache", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.interruptedTxCache = &vm.TxCache{
|
||||||
|
Cache: interruptedTxCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !worker.interruptCommitFlag {
|
||||||
|
worker.noempty = 0
|
||||||
|
}
|
||||||
|
|
||||||
// Sanitize recommit interval if the user-specified one is too short.
|
// Sanitize recommit interval if the user-specified one is too short.
|
||||||
recommit := worker.config.Recommit
|
recommit := worker.config.Recommit
|
||||||
if recommit < minRecommitInterval {
|
if recommit < minRecommitInterval {
|
||||||
|
|
@ -248,7 +309,7 @@ func newWorkerWithDelay(config *Config, chainConfig *params.ChainConfig, engine
|
||||||
|
|
||||||
worker.wg.Add(4)
|
worker.wg.Add(4)
|
||||||
|
|
||||||
go worker.mainLoopWithDelay(ctx, delay)
|
go worker.mainLoopWithDelay(ctx, delay, opcodeDelay)
|
||||||
go worker.newWorkLoop(ctx, recommit)
|
go worker.newWorkLoop(ctx, recommit)
|
||||||
go worker.resultLoop()
|
go worker.resultLoop()
|
||||||
go worker.taskLoop()
|
go worker.taskLoop()
|
||||||
|
|
@ -261,8 +322,9 @@ func newWorkerWithDelay(config *Config, chainConfig *params.ChainConfig, engine
|
||||||
return worker
|
return worker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mainLoopWithDelay is mainLoop() with extra params to induce artficial delays for tests such as commit-interrupt.
|
||||||
// nolint:gocognit
|
// nolint:gocognit
|
||||||
func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint) {
|
func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint, opcodeDelay uint) {
|
||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
defer w.txsSub.Unsubscribe()
|
defer w.txsSub.Unsubscribe()
|
||||||
defer w.chainHeadSub.Unsubscribe()
|
defer w.chainHeadSub.Unsubscribe()
|
||||||
|
|
@ -280,7 +342,7 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint) {
|
||||||
select {
|
select {
|
||||||
case req := <-w.newWorkCh:
|
case req := <-w.newWorkCh:
|
||||||
//nolint:contextcheck
|
//nolint:contextcheck
|
||||||
w.commitWorkWithDelay(req.ctx, req.interrupt, req.noempty, req.timestamp, delay)
|
w.commitWorkWithDelay(req.ctx, req.interrupt, req.noempty, req.timestamp, delay, opcodeDelay)
|
||||||
|
|
||||||
case req := <-w.getWorkCh:
|
case req := <-w.getWorkCh:
|
||||||
//nolint:contextcheck
|
//nolint:contextcheck
|
||||||
|
|
@ -342,6 +404,7 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint) {
|
||||||
// Note all transactions received may not be continuous with transactions
|
// Note all transactions received may not be continuous with transactions
|
||||||
// already included in the current sealing block. These transactions will
|
// already included in the current sealing block. These transactions will
|
||||||
// be automatically eliminated.
|
// be automatically eliminated.
|
||||||
|
// nolint : nestif
|
||||||
if !w.isRunning() && w.current != nil {
|
if !w.isRunning() && w.current != nil {
|
||||||
// If block is already full, abort
|
// If block is already full, abort
|
||||||
if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas {
|
if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas {
|
||||||
|
|
@ -358,7 +421,6 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint) {
|
||||||
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
|
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
|
||||||
tcount := w.current.tcount
|
tcount := w.current.tcount
|
||||||
|
|
||||||
//nolint:contextcheck
|
|
||||||
w.commitTransactions(w.current, txset, nil, context.Background())
|
w.commitTransactions(w.current, txset, nil, context.Background())
|
||||||
|
|
||||||
// Only update the snapshot if any new transactions were added
|
// Only update the snapshot if any new transactions were added
|
||||||
|
|
@ -390,170 +452,8 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:gocognit
|
// commitWorkWithDelay is commitWork() with extra params to induce artficial delays for tests such as commit-interrupt.
|
||||||
func (w *worker) commitTransactionsWithDelay(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32, interruptCtx context.Context, delay uint) bool {
|
func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noempty bool, timestamp int64, delay uint, opcodeDelay uint) {
|
||||||
gasLimit := env.header.GasLimit
|
|
||||||
if env.gasPool == nil {
|
|
||||||
env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
var coalescedLogs []*types.Log
|
|
||||||
|
|
||||||
initialGasLimit := env.gasPool.Gas()
|
|
||||||
initialTxs := txs.GetTxs()
|
|
||||||
|
|
||||||
var breakCause string
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
log.OnDebug(func(lg log.Logging) {
|
|
||||||
lg("commitTransactions-stats",
|
|
||||||
"initialTxsCount", initialTxs,
|
|
||||||
"initialGasLimit", initialGasLimit,
|
|
||||||
"resultTxsCount", txs.GetTxs(),
|
|
||||||
"resultGapPool", env.gasPool.Gas(),
|
|
||||||
"exitCause", breakCause)
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
mainloop:
|
|
||||||
for {
|
|
||||||
// case of interrupting by timeout
|
|
||||||
if interruptCtx != nil {
|
|
||||||
// case of interrupting by timeout
|
|
||||||
select {
|
|
||||||
case <-interruptCtx.Done():
|
|
||||||
commitInterruptCounter.Inc(1)
|
|
||||||
log.Warn("Tx Level Interrupt")
|
|
||||||
break mainloop
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// In the following three cases, we will interrupt the execution of the transaction.
|
|
||||||
// (1) new head block event arrival, the interrupt signal is 1
|
|
||||||
// (2) worker start or restart, the interrupt signal is 1
|
|
||||||
// (3) worker recreate the sealing block with any newly arrived transactions, the interrupt signal is 2.
|
|
||||||
// For the first two cases, the semi-finished work will be discarded.
|
|
||||||
// For the third case, the semi-finished work will be submitted to the consensus engine.
|
|
||||||
if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
|
|
||||||
// Notify resubmit loop to increase resubmitting interval due to too frequent commits.
|
|
||||||
if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
|
|
||||||
ratio := float64(gasLimit-env.gasPool.Gas()) / float64(gasLimit)
|
|
||||||
if ratio < 0.1 {
|
|
||||||
// nolint:goconst
|
|
||||||
ratio = 0.1
|
|
||||||
}
|
|
||||||
w.resubmitAdjustCh <- &intervalAdjust{
|
|
||||||
ratio: ratio,
|
|
||||||
inc: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// nolint:goconst
|
|
||||||
breakCause = "interrupt"
|
|
||||||
return atomic.LoadInt32(interrupt) == commitInterruptNewHead
|
|
||||||
}
|
|
||||||
// If we don't have enough gas for any further transactions then we're done
|
|
||||||
if env.gasPool.Gas() < params.TxGas {
|
|
||||||
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
|
||||||
// nolint:goconst
|
|
||||||
breakCause = "Not enough gas for further transactions"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Retrieve the next transaction and abort if all done
|
|
||||||
tx := txs.Peek()
|
|
||||||
if tx == nil {
|
|
||||||
// nolint:goconst
|
|
||||||
breakCause = "all transactions has been included"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Error may be ignored here. The error has already been checked
|
|
||||||
// during transaction acceptance is the transaction pool.
|
|
||||||
//
|
|
||||||
// We use the eip155 signer regardless of the current hf.
|
|
||||||
from, _ := types.Sender(env.signer, tx)
|
|
||||||
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
|
||||||
// phase, start ignoring the sender until we do.
|
|
||||||
if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
|
|
||||||
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
|
|
||||||
|
|
||||||
txs.Pop()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Start executing the transaction
|
|
||||||
env.state.Prepare(tx.Hash(), env.tcount)
|
|
||||||
|
|
||||||
var start time.Time
|
|
||||||
|
|
||||||
log.OnDebug(func(log.Logging) {
|
|
||||||
start = time.Now()
|
|
||||||
})
|
|
||||||
|
|
||||||
logs, err := w.commitTransaction(env, tx)
|
|
||||||
time.Sleep(time.Duration(delay) * time.Millisecond)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, core.ErrGasLimitReached):
|
|
||||||
// Pop the current out-of-gas transaction without shifting in the next from the account
|
|
||||||
log.Trace("Gas limit exceeded for current block", "sender", from)
|
|
||||||
txs.Pop()
|
|
||||||
|
|
||||||
case errors.Is(err, core.ErrNonceTooLow):
|
|
||||||
// New head notification data race between the transaction pool and miner, shift
|
|
||||||
log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
|
|
||||||
txs.Shift()
|
|
||||||
|
|
||||||
case errors.Is(err, core.ErrNonceTooHigh):
|
|
||||||
// Reorg notification data race between the transaction pool and miner, skip account =
|
|
||||||
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
|
|
||||||
txs.Pop()
|
|
||||||
|
|
||||||
case errors.Is(err, nil):
|
|
||||||
// Everything ok, collect the logs and shift in the next transaction from the same account
|
|
||||||
coalescedLogs = append(coalescedLogs, logs...)
|
|
||||||
env.tcount++
|
|
||||||
txs.Shift()
|
|
||||||
|
|
||||||
log.OnDebug(func(lg log.Logging) {
|
|
||||||
lg("Committed new tx", "tx hash", tx.Hash(), "from", from, "to", tx.To(), "nonce", tx.Nonce(), "gas", tx.Gas(), "gasPrice", tx.GasPrice(), "value", tx.Value(), "time spent", time.Since(start))
|
|
||||||
})
|
|
||||||
|
|
||||||
case errors.Is(err, core.ErrTxTypeNotSupported):
|
|
||||||
// Pop the unsupported transaction without shifting in the next from the account
|
|
||||||
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
|
|
||||||
txs.Pop()
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Strange error, discard the transaction and get the next in line (note, the
|
|
||||||
// nonce-too-high clause will prevent us from executing in vain).
|
|
||||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
|
||||||
txs.Shift()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !w.isRunning() && len(coalescedLogs) > 0 {
|
|
||||||
// We don't push the pendingLogsEvent while we are sealing. The reason is that
|
|
||||||
// when we are sealing, the worker will regenerate a sealing block every 3 seconds.
|
|
||||||
// In order to avoid pushing the repeated pendingLog, we disable the pending log pushing.
|
|
||||||
// make a copy, the state caches the logs and these logs get "upgraded" from pending to mined
|
|
||||||
// logs by filling in the block hash when the block was mined by the local miner. This can
|
|
||||||
// cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed.
|
|
||||||
cpy := make([]*types.Log, len(coalescedLogs))
|
|
||||||
for i, l := range coalescedLogs {
|
|
||||||
cpy[i] = new(types.Log)
|
|
||||||
*cpy[i] = *l
|
|
||||||
}
|
|
||||||
|
|
||||||
w.pendingLogsFeed.Send(cpy)
|
|
||||||
}
|
|
||||||
// Notify resubmit loop to decrease resubmitting interval if current interval is larger
|
|
||||||
// than the user-specified one.
|
|
||||||
if interrupt != nil {
|
|
||||||
w.resubmitAdjustCh <- &intervalAdjust{inc: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noempty bool, timestamp int64, delay uint) {
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -591,8 +491,14 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
|
||||||
stopFn()
|
stopFn()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !noempty {
|
if !noempty && w.interruptCommitFlag {
|
||||||
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||||
|
// nolint : staticcheck
|
||||||
|
interruptCtx = vm.PutCache(interruptCtx, w.interruptedTxCache)
|
||||||
|
// nolint : staticcheck
|
||||||
|
interruptCtx = context.WithValue(interruptCtx, vm.InterruptCtxDelayKey, delay)
|
||||||
|
// nolint : staticcheck
|
||||||
|
interruptCtx = context.WithValue(interruptCtx, vm.InterruptCtxOpcodeDelayKey, opcodeDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, span := tracing.StartSpan(ctx, "commitWork")
|
ctx, span := tracing.StartSpan(ctx, "commitWork")
|
||||||
|
|
@ -613,7 +519,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill pending transactions from the txpool
|
// Fill pending transactions from the txpool
|
||||||
w.fillTransactionsWithDelay(ctx, interrupt, work, interruptCtx, delay)
|
w.fillTransactionsWithDelay(ctx, interrupt, work, interruptCtx)
|
||||||
|
|
||||||
err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start)
|
err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -629,8 +535,9 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
|
||||||
w.current = work
|
w.current = work
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fillTransactionsWithDelay is fillTransactions() with extra params to induce artficial delays for tests such as commit-interrupt.
|
||||||
// nolint:gocognit
|
// nolint:gocognit
|
||||||
func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32, env *environment, interruptCtx context.Context, delay uint) {
|
func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32, env *environment, interruptCtx context.Context) {
|
||||||
ctx, span := tracing.StartSpan(ctx, "fillTransactions")
|
ctx, span := tracing.StartSpan(ctx, "fillTransactions")
|
||||||
defer tracing.EndSpan(span)
|
defer tracing.EndSpan(span)
|
||||||
|
|
||||||
|
|
@ -754,7 +661,7 @@ func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32
|
||||||
})
|
})
|
||||||
|
|
||||||
tracing.Exec(ctx, "", "worker.LocalCommitTransactions", func(ctx context.Context, span trace.Span) {
|
tracing.Exec(ctx, "", "worker.LocalCommitTransactions", func(ctx context.Context, span trace.Span) {
|
||||||
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx, delay)
|
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx)
|
||||||
})
|
})
|
||||||
|
|
||||||
if committed {
|
if committed {
|
||||||
|
|
@ -777,7 +684,7 @@ func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32
|
||||||
})
|
})
|
||||||
|
|
||||||
tracing.Exec(ctx, "", "worker.RemoteCommitTransactions", func(ctx context.Context, span trace.Span) {
|
tracing.Exec(ctx, "", "worker.RemoteCommitTransactions", func(ctx context.Context, span trace.Span) {
|
||||||
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx, delay)
|
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx)
|
||||||
})
|
})
|
||||||
|
|
||||||
if committed {
|
if committed {
|
||||||
|
|
@ -793,3 +700,176 @@ func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32
|
||||||
attribute.Int("len of final remote txs", remoteEnvTCount),
|
attribute.Int("len of final remote txs", remoteEnvTCount),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// commitTransactionsWithDelay is commitTransactions() with extra params to induce artficial delays for tests such as commit-interrupt.
|
||||||
|
// nolint:gocognit, unparam
|
||||||
|
func (w *worker) commitTransactionsWithDelay(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32, interruptCtx context.Context) bool {
|
||||||
|
gasLimit := env.header.GasLimit
|
||||||
|
if env.gasPool == nil {
|
||||||
|
env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
var coalescedLogs []*types.Log
|
||||||
|
|
||||||
|
initialGasLimit := env.gasPool.Gas()
|
||||||
|
initialTxs := txs.GetTxs()
|
||||||
|
|
||||||
|
var breakCause string
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
log.OnDebug(func(lg log.Logging) {
|
||||||
|
lg("commitTransactions-stats",
|
||||||
|
"initialTxsCount", initialTxs,
|
||||||
|
"initialGasLimit", initialGasLimit,
|
||||||
|
"resultTxsCount", txs.GetTxs(),
|
||||||
|
"resultGapPool", env.gasPool.Gas(),
|
||||||
|
"exitCause", breakCause)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
mainloop:
|
||||||
|
for {
|
||||||
|
if interruptCtx != nil {
|
||||||
|
// case of interrupting by timeout
|
||||||
|
select {
|
||||||
|
case <-interruptCtx.Done():
|
||||||
|
log.Warn("Interrupt")
|
||||||
|
break mainloop
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the following three cases, we will interrupt the execution of the transaction.
|
||||||
|
// (1) new head block event arrival, the interrupt signal is 1
|
||||||
|
// (2) worker start or restart, the interrupt signal is 1
|
||||||
|
// (3) worker recreate the sealing block with any newly arrived transactions, the interrupt signal is 2.
|
||||||
|
// For the first two cases, the semi-finished work will be discarded.
|
||||||
|
// For the third case, the semi-finished work will be submitted to the consensus engine.
|
||||||
|
if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
|
||||||
|
// Notify resubmit loop to increase resubmitting interval due to too frequent commits.
|
||||||
|
if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
|
||||||
|
ratio := float64(gasLimit-env.gasPool.Gas()) / float64(gasLimit)
|
||||||
|
if ratio < 0.1 {
|
||||||
|
// nolint:goconst
|
||||||
|
ratio = 0.1
|
||||||
|
}
|
||||||
|
w.resubmitAdjustCh <- &intervalAdjust{
|
||||||
|
ratio: ratio,
|
||||||
|
inc: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// nolint:goconst
|
||||||
|
breakCause = "interrupt"
|
||||||
|
|
||||||
|
return atomic.LoadInt32(interrupt) == commitInterruptNewHead
|
||||||
|
}
|
||||||
|
// If we don't have enough gas for any further transactions then we're done
|
||||||
|
if env.gasPool.Gas() < params.TxGas {
|
||||||
|
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
||||||
|
// nolint:goconst
|
||||||
|
breakCause = "Not enough gas for further transactions"
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Retrieve the next transaction and abort if all done
|
||||||
|
tx := txs.Peek()
|
||||||
|
if tx == nil {
|
||||||
|
// nolint:goconst
|
||||||
|
breakCause = "all transactions has been included"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Error may be ignored here. The error has already been checked
|
||||||
|
// during transaction acceptance is the transaction pool.
|
||||||
|
//
|
||||||
|
// We use the eip155 signer regardless of the current hf.
|
||||||
|
from, _ := types.Sender(env.signer, tx)
|
||||||
|
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
||||||
|
// phase, start ignoring the sender until we do.
|
||||||
|
if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
|
||||||
|
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
|
||||||
|
|
||||||
|
txs.Pop()
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Start executing the transaction
|
||||||
|
env.state.Prepare(tx.Hash(), env.tcount)
|
||||||
|
|
||||||
|
var start time.Time
|
||||||
|
|
||||||
|
log.OnDebug(func(log.Logging) {
|
||||||
|
start = time.Now()
|
||||||
|
})
|
||||||
|
|
||||||
|
logs, err := w.commitTransaction(env, tx, interruptCtx)
|
||||||
|
|
||||||
|
if interruptCtx != nil {
|
||||||
|
if delay := interruptCtx.Value(vm.InterruptCtxDelayKey); delay != nil {
|
||||||
|
// nolint : durationcheck
|
||||||
|
time.Sleep(time.Duration(delay.(uint)) * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, core.ErrGasLimitReached):
|
||||||
|
// Pop the current out-of-gas transaction without shifting in the next from the account
|
||||||
|
log.Trace("Gas limit exceeded for current block", "sender", from)
|
||||||
|
txs.Pop()
|
||||||
|
|
||||||
|
case errors.Is(err, core.ErrNonceTooLow):
|
||||||
|
// New head notification data race between the transaction pool and miner, shift
|
||||||
|
log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
|
||||||
|
txs.Shift()
|
||||||
|
|
||||||
|
case errors.Is(err, core.ErrNonceTooHigh):
|
||||||
|
// Reorg notification data race between the transaction pool and miner, skip account =
|
||||||
|
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
|
||||||
|
txs.Pop()
|
||||||
|
|
||||||
|
case errors.Is(err, nil):
|
||||||
|
// Everything ok, collect the logs and shift in the next transaction from the same account
|
||||||
|
coalescedLogs = append(coalescedLogs, logs...)
|
||||||
|
env.tcount++
|
||||||
|
|
||||||
|
txs.Shift()
|
||||||
|
|
||||||
|
log.OnDebug(func(lg log.Logging) {
|
||||||
|
lg("Committed new tx", "tx hash", tx.Hash(), "from", from, "to", tx.To(), "nonce", tx.Nonce(), "gas", tx.Gas(), "gasPrice", tx.GasPrice(), "value", tx.Value(), "time spent", time.Since(start))
|
||||||
|
})
|
||||||
|
|
||||||
|
case errors.Is(err, core.ErrTxTypeNotSupported):
|
||||||
|
// Pop the unsupported transaction without shifting in the next from the account
|
||||||
|
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
|
||||||
|
txs.Pop()
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Strange error, discard the transaction and get the next in line (note, the
|
||||||
|
// nonce-too-high clause will prevent us from executing in vain).
|
||||||
|
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||||
|
txs.Shift()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !w.isRunning() && len(coalescedLogs) > 0 {
|
||||||
|
// We don't push the pendingLogsEvent while we are sealing. The reason is that
|
||||||
|
// when we are sealing, the worker will regenerate a sealing block every 3 seconds.
|
||||||
|
// In order to avoid pushing the repeated pendingLog, we disable the pending log pushing.
|
||||||
|
// make a copy, the state caches the logs and these logs get "upgraded" from pending to mined
|
||||||
|
// logs by filling in the block hash when the block was mined by the local miner. This can
|
||||||
|
// cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed.
|
||||||
|
cpy := make([]*types.Log, len(coalescedLogs))
|
||||||
|
for i, l := range coalescedLogs {
|
||||||
|
cpy[i] = new(types.Log)
|
||||||
|
*cpy[i] = *l
|
||||||
|
}
|
||||||
|
|
||||||
|
w.pendingLogsFeed.Send(cpy)
|
||||||
|
}
|
||||||
|
// Notify resubmit loop to decrease resubmitting interval if current interval is larger
|
||||||
|
// than the user-specified one.
|
||||||
|
if interrupt != nil {
|
||||||
|
w.resubmitAdjustCh <- &intervalAdjust{inc: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
159
miner/worker.go
159
miner/worker.go
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mapset "github.com/deckarep/golang-set"
|
mapset "github.com/deckarep/golang-set"
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
@ -42,8 +43,10 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/bor"
|
"github.com/ethereum/go-ethereum/consensus/bor"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"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/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
|
@ -95,7 +98,7 @@ const (
|
||||||
var (
|
var (
|
||||||
sealedBlocksCounter = metrics.NewRegisteredCounter("worker/sealedBlocks", nil)
|
sealedBlocksCounter = metrics.NewRegisteredCounter("worker/sealedBlocks", nil)
|
||||||
sealedEmptyBlocksCounter = metrics.NewRegisteredCounter("worker/sealedEmptyBlocks", nil)
|
sealedEmptyBlocksCounter = metrics.NewRegisteredCounter("worker/sealedEmptyBlocks", nil)
|
||||||
commitInterruptCounter = metrics.NewRegisteredCounter("worker/commitInterrupt", nil)
|
txCommitInterruptCounter = metrics.NewRegisteredCounter("worker/txCommitInterrupt", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// environment is the worker's current environment and holds all
|
// environment is the worker's current environment and holds all
|
||||||
|
|
@ -274,6 +277,8 @@ type worker struct {
|
||||||
resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval.
|
resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval.
|
||||||
|
|
||||||
profileCount *int32 // Global count for profiling
|
profileCount *int32 // Global count for profiling
|
||||||
|
interruptCommitFlag bool // Interrupt commit ( Default true )
|
||||||
|
interruptedTxCache *vm.TxCache
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
|
|
@ -302,6 +307,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
|
||||||
resubmitIntervalCh: make(chan time.Duration),
|
resubmitIntervalCh: make(chan time.Duration),
|
||||||
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
|
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
|
||||||
noempty: 1,
|
noempty: 1,
|
||||||
|
interruptCommitFlag: config.CommitInterruptFlag,
|
||||||
}
|
}
|
||||||
worker.profileCount = new(int32)
|
worker.profileCount = new(int32)
|
||||||
// Subscribe NewTxsEvent for tx pool
|
// Subscribe NewTxsEvent for tx pool
|
||||||
|
|
@ -310,6 +316,19 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
|
||||||
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
|
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
|
||||||
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
|
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
|
||||||
|
|
||||||
|
interruptedTxCache, err := lru.New(vm.InterruptedTxCacheSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Failed to create interrupted tx cache", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.interruptedTxCache = &vm.TxCache{
|
||||||
|
Cache: interruptedTxCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !worker.interruptCommitFlag {
|
||||||
|
worker.noempty = 0
|
||||||
|
}
|
||||||
|
|
||||||
// Sanitize recommit interval if the user-specified one is too short.
|
// Sanitize recommit interval if the user-specified one is too short.
|
||||||
recommit := worker.config.Recommit
|
recommit := worker.config.Recommit
|
||||||
if recommit < minRecommitInterval {
|
if recommit < minRecommitInterval {
|
||||||
|
|
@ -929,10 +948,12 @@ func (w *worker) updateSnapshot(env *environment) {
|
||||||
w.snapshotState = env.state.Copy()
|
w.snapshotState = env.state.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
|
func (w *worker) commitTransaction(env *environment, tx *types.Transaction, interruptCtx context.Context) ([]*types.Log, error) {
|
||||||
snap := env.state.Snapshot()
|
snap := env.state.Snapshot()
|
||||||
|
|
||||||
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig())
|
// nolint : staticcheck
|
||||||
|
interruptCtx = vm.SetCurrentTxOnContext(interruptCtx, tx.Hash())
|
||||||
|
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig(), interruptCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.state.RevertToSnapshot(snap)
|
env.state.RevertToSnapshot(snap)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -951,6 +972,47 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
|
||||||
}
|
}
|
||||||
var coalescedLogs []*types.Log
|
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
|
||||||
|
|
||||||
|
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()
|
initialGasLimit := env.gasPool.Gas()
|
||||||
initialTxs := txs.GetTxs()
|
initialTxs := txs.GetTxs()
|
||||||
|
|
||||||
|
|
@ -969,12 +1031,15 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
|
||||||
|
|
||||||
mainloop:
|
mainloop:
|
||||||
for {
|
for {
|
||||||
// case of interrupting by timeout
|
|
||||||
if interruptCtx != nil {
|
if interruptCtx != nil {
|
||||||
|
if EnableMVHashMap {
|
||||||
|
env.state.AddEmptyMVHashMap()
|
||||||
|
}
|
||||||
|
|
||||||
// case of interrupting by timeout
|
// case of interrupting by timeout
|
||||||
select {
|
select {
|
||||||
case <-interruptCtx.Done():
|
case <-interruptCtx.Done():
|
||||||
commitInterruptCounter.Inc(1)
|
txCommitInterruptCounter.Inc(1)
|
||||||
log.Warn("Tx Level Interrupt")
|
log.Warn("Tx Level Interrupt")
|
||||||
break mainloop
|
break mainloop
|
||||||
default:
|
default:
|
||||||
|
|
@ -1038,7 +1103,7 @@ mainloop:
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
})
|
})
|
||||||
|
|
||||||
logs, err := w.commitTransaction(env, tx)
|
logs, err := w.commitTransaction(env, tx, interruptCtx)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, core.ErrGasLimitReached):
|
case errors.Is(err, core.ErrGasLimitReached):
|
||||||
|
|
@ -1060,6 +1125,22 @@ mainloop:
|
||||||
// Everything ok, collect the logs and shift in the next transaction from the same account
|
// Everything ok, collect the logs and shift in the next transaction from the same account
|
||||||
coalescedLogs = append(coalescedLogs, logs...)
|
coalescedLogs = append(coalescedLogs, logs...)
|
||||||
env.tcount++
|
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()
|
txs.Shift()
|
||||||
|
|
||||||
log.OnDebug(func(lg log.Logging) {
|
log.OnDebug(func(lg log.Logging) {
|
||||||
|
|
@ -1077,6 +1158,50 @@ mainloop:
|
||||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||||
txs.Shift()
|
txs.Shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if !w.isRunning() && len(coalescedLogs) > 0 {
|
||||||
|
|
@ -1441,8 +1566,20 @@ func (w *worker) generateWork(ctx context.Context, params *generateParams) (*typ
|
||||||
}
|
}
|
||||||
defer work.discard()
|
defer work.discard()
|
||||||
|
|
||||||
interruptCtx, stopFn := getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
// nolint : contextcheck
|
||||||
defer stopFn()
|
var interruptCtx = context.Background()
|
||||||
|
|
||||||
|
stopFn := func() {}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
stopFn()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if w.interruptCommitFlag {
|
||||||
|
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||||
|
// nolint : staticcheck
|
||||||
|
interruptCtx = vm.PutCache(interruptCtx, w.interruptedTxCache)
|
||||||
|
}
|
||||||
|
|
||||||
w.fillTransactions(ctx, nil, work, interruptCtx)
|
w.fillTransactions(ctx, nil, work, interruptCtx)
|
||||||
|
|
||||||
|
|
@ -1482,7 +1619,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:contextcheck
|
// nolint:contextcheck
|
||||||
var interruptCtx = context.Background()
|
var interruptCtx = context.Background()
|
||||||
|
|
||||||
stopFn := func() {}
|
stopFn := func() {}
|
||||||
|
|
@ -1490,8 +1627,10 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
|
||||||
stopFn()
|
stopFn()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !noempty {
|
if !noempty && w.interruptCommitFlag {
|
||||||
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||||
|
// nolint : staticcheck
|
||||||
|
interruptCtx = vm.PutCache(interruptCtx, w.interruptedTxCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, span := tracing.StartSpan(ctx, "commitWork")
|
ctx, span := tracing.StartSpan(ctx, "commitWork")
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package miner
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -87,7 +86,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool, isBor bool) {
|
||||||
|
|
||||||
chainConfig.LondonBlock = big.NewInt(0)
|
chainConfig.LondonBlock = big.NewInt(0)
|
||||||
|
|
||||||
w, b, _ := NewTestWorker(t, chainConfig, engine, db, 0, 0, 0)
|
w, b, _ := NewTestWorker(t, chainConfig, engine, db, 0, 0, 0, 0)
|
||||||
defer w.close()
|
defer w.close()
|
||||||
|
|
||||||
// This test chain imports the mined blocks.
|
// This test chain imports the mined blocks.
|
||||||
|
|
@ -193,7 +192,7 @@ func TestEmptyWorkClique(t *testing.T) {
|
||||||
func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
|
|
||||||
w, _, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0)
|
w, _, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0, 0)
|
||||||
defer w.close()
|
defer w.close()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -247,7 +246,7 @@ func TestStreamUncleBlock(t *testing.T) {
|
||||||
ethash := ethash.NewFaker()
|
ethash := ethash.NewFaker()
|
||||||
defer ethash.Close()
|
defer ethash.Close()
|
||||||
|
|
||||||
w, b, _ := NewTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1, 0, 0)
|
w, b, _ := NewTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1, 0, 0, 0)
|
||||||
defer w.close()
|
defer w.close()
|
||||||
|
|
||||||
var taskCh = make(chan struct{})
|
var taskCh = make(chan struct{})
|
||||||
|
|
@ -309,7 +308,7 @@ func TestRegenerateMiningBlockClique(t *testing.T) {
|
||||||
func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
|
|
||||||
w, b, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0)
|
w, b, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0, 0)
|
||||||
defer w.close()
|
defer w.close()
|
||||||
|
|
||||||
var taskCh = make(chan struct{}, 3)
|
var taskCh = make(chan struct{}, 3)
|
||||||
|
|
@ -380,7 +379,7 @@ func TestAdjustIntervalClique(t *testing.T) {
|
||||||
func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
|
|
||||||
w, _, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0)
|
w, _, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0, 0)
|
||||||
defer w.close()
|
defer w.close()
|
||||||
|
|
||||||
w.skipSealHook = func(task *task) bool {
|
w.skipSealHook = func(task *task) bool {
|
||||||
|
|
@ -488,7 +487,7 @@ func TestGetSealingWorkPostMerge(t *testing.T) {
|
||||||
func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, postMerge bool) {
|
func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, postMerge bool) {
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
|
|
||||||
w, b, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0)
|
w, b, _ := NewTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, 0, 0, 0)
|
||||||
defer w.close()
|
defer w.close()
|
||||||
|
|
||||||
w.setExtra([]byte{0x01, 0x02})
|
w.setExtra([]byte{0x01, 0x02})
|
||||||
|
|
@ -624,23 +623,41 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:paralleltest
|
// nolint : paralleltest
|
||||||
|
// TestCommitInterruptExperimentBor tests the commit interrupt experiment for bor consensus by inducing an artificial delay at transaction level.
|
||||||
func TestCommitInterruptExperimentBor(t *testing.T) {
|
func TestCommitInterruptExperimentBor(t *testing.T) {
|
||||||
// with 1 sec block time and 200 millisec tx delay we should get 5 txs per block
|
// with 1 sec block time and 200 millisec tx delay we should get 5 txs per block
|
||||||
testCommitInterruptExperimentBor(t, 200, 5)
|
testCommitInterruptExperimentBor(t, 200, 5, 0)
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
// with 1 sec block time and 100 millisec tx delay we should get 10 txs per block
|
// with 1 sec block time and 100 millisec tx delay we should get 10 txs per block
|
||||||
testCommitInterruptExperimentBor(t, 100, 10)
|
testCommitInterruptExperimentBor(t, 100, 10, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:thelper
|
// nolint : paralleltest
|
||||||
func testCommitInterruptExperimentBor(t *testing.T, delay uint, txCount int) {
|
// TestCommitInterruptExperimentBorContract tests the commit interrupt experiment for bor consensus by inducing an artificial delay at OPCODE level.
|
||||||
|
func TestCommitInterruptExperimentBorContract(t *testing.T) {
|
||||||
|
// pre-calculated number of OPCODES = 123. 7*123=861 < 1000, 1 tx is possible but 2 tx per block will not be possible.
|
||||||
|
testCommitInterruptExperimentBorContract(t, 0, 1, 7)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
// pre-calculated number of OPCODES = 123. 2*123=246 < 1000, 4 tx is possible but 5 tx per block will not be possible. But 3 happen due to other overheads.
|
||||||
|
testCommitInterruptExperimentBorContract(t, 0, 3, 2)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
// pre-calculated number of OPCODES = 123. 3*123=369 < 1000, 2 tx is possible but 3 tx per block will not be possible.
|
||||||
|
testCommitInterruptExperimentBorContract(t, 0, 2, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint : thelper
|
||||||
|
// testCommitInterruptExperimentBorContract is a helper function for testing the commit interrupt experiment for bor consensus.
|
||||||
|
func testCommitInterruptExperimentBorContract(t *testing.T, delay uint, txCount int, opcodeDelay uint) {
|
||||||
var (
|
var (
|
||||||
engine consensus.Engine
|
engine consensus.Engine
|
||||||
chainConfig *params.ChainConfig
|
chainConfig *params.ChainConfig
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
|
txInTxpool = 100
|
||||||
|
txs = make([]*types.Transaction, 0, txInTxpool)
|
||||||
)
|
)
|
||||||
|
|
||||||
chainConfig = params.BorUnittestChainConfig
|
chainConfig = params.BorUnittestChainConfig
|
||||||
|
|
@ -648,38 +665,91 @@ func testCommitInterruptExperimentBor(t *testing.T, delay uint, txCount int) {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(4, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.Root().SetHandler(log.LvlFilterHandler(4, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||||
|
|
||||||
engine, ctrl = getFakeBorFromConfig(t, chainConfig)
|
engine, ctrl = getFakeBorFromConfig(t, chainConfig)
|
||||||
|
|
||||||
|
w, b, _ := NewTestWorker(t, chainConfig, engine, db, 0, 1, delay, opcodeDelay)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
w.close()
|
||||||
engine.Close()
|
engine.Close()
|
||||||
|
db.Close()
|
||||||
ctrl.Finish()
|
ctrl.Finish()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
w, b, _ := NewTestWorker(t, chainConfig, engine, db, 0, 1, delay)
|
// nonce 0 tx
|
||||||
defer w.close()
|
tx, addr := b.newStorageCreateContractTx()
|
||||||
|
|
||||||
wg := new(sync.WaitGroup)
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
wg.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
tx := b.newRandomTx(false)
|
|
||||||
if err := b.TxPool().AddRemote(tx); err != nil {
|
if err := b.TxPool().AddRemote(tx); err != nil {
|
||||||
t.Log(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(20 * time.Millisecond)
|
time.Sleep(4 * time.Second)
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
// nonce starts from 1 because we already have one tx
|
||||||
|
initNonce := uint64(1)
|
||||||
|
|
||||||
|
for i := 0; i < txInTxpool; i++ {
|
||||||
|
tx := b.newStorageContractCallTx(addr, initNonce+uint64(i))
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.TxPool().AddRemotes(txs); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Start mining!
|
// Start mining!
|
||||||
w.start()
|
w.start()
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
w.stop()
|
w.stop()
|
||||||
|
|
||||||
assert.Equal(t, txCount, w.chain.CurrentBlock().Transactions().Len())
|
currentBlockNumber := w.current.header.Number.Uint64()
|
||||||
|
assert.Check(t, txCount >= w.chain.GetBlockByNumber(currentBlockNumber-1).Transactions().Len())
|
||||||
|
assert.Check(t, 0 < w.chain.GetBlockByNumber(currentBlockNumber-1).Transactions().Len()+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint : thelper
|
||||||
|
// testCommitInterruptExperimentBor is a helper function for testing the commit interrupt experiment for bor consensus.
|
||||||
|
func testCommitInterruptExperimentBor(t *testing.T, delay uint, txCount int, opcodeDelay uint) {
|
||||||
|
var (
|
||||||
|
engine consensus.Engine
|
||||||
|
chainConfig *params.ChainConfig
|
||||||
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
txInTxpool = 100
|
||||||
|
txs = make([]*types.Transaction, 0, txInTxpool)
|
||||||
|
)
|
||||||
|
|
||||||
|
chainConfig = params.BorUnittestChainConfig
|
||||||
|
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(4, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||||
|
|
||||||
|
engine, ctrl = getFakeBorFromConfig(t, chainConfig)
|
||||||
|
|
||||||
|
w, b, _ := NewTestWorker(t, chainConfig, engine, db, 0, 1, delay, opcodeDelay)
|
||||||
|
defer func() {
|
||||||
|
w.close()
|
||||||
|
engine.Close()
|
||||||
|
db.Close()
|
||||||
|
ctrl.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// nonce starts from 0 because have no txs yet
|
||||||
|
initNonce := uint64(0)
|
||||||
|
|
||||||
|
for i := 0; i < txInTxpool; i++ {
|
||||||
|
tx := b.newRandomTxWithNonce(false, initNonce+uint64(i))
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.TxPool().AddRemotes(txs); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start mining!
|
||||||
|
w.start()
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
w.stop()
|
||||||
|
|
||||||
|
currentBlockNumber := w.current.header.Number.Uint64()
|
||||||
|
assert.Check(t, txCount >= w.chain.GetBlockByNumber(currentBlockNumber-1).Transactions().Len())
|
||||||
|
assert.Check(t, 0 < w.chain.GetBlockByNumber(currentBlockNumber-1).Transactions().Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkBorMining(b *testing.B) {
|
func BenchmarkBorMining(b *testing.B) {
|
||||||
|
|
@ -713,7 +783,7 @@ func BenchmarkBorMining(b *testing.B) {
|
||||||
|
|
||||||
chainConfig.LondonBlock = big.NewInt(0)
|
chainConfig.LondonBlock = big.NewInt(0)
|
||||||
|
|
||||||
w, back, _ := NewTestWorker(b, chainConfig, engine, db, 0, 0, 0)
|
w, back, _ := NewTestWorker(b, chainConfig, engine, db, 0, 0, 0, 0)
|
||||||
defer w.close()
|
defer w.close()
|
||||||
|
|
||||||
// This test chain imports the mined blocks.
|
// This test chain imports the mined blocks.
|
||||||
|
|
@ -779,3 +849,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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
|
|
@ -63,6 +64,7 @@ var DefaultConfig = Config{
|
||||||
ListenAddr: ":30303",
|
ListenAddr: ":30303",
|
||||||
MaxPeers: 50,
|
MaxPeers: 50,
|
||||||
NAT: nat.Any(),
|
NAT: nat.Any(),
|
||||||
|
TxArrivalWait: 500 * time.Millisecond,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,10 @@ type Config struct {
|
||||||
Logger log.Logger `toml:",omitempty"`
|
Logger log.Logger `toml:",omitempty"`
|
||||||
|
|
||||||
clock mclock.Clock
|
clock mclock.Clock
|
||||||
|
|
||||||
|
// TxArrivalWait is the duration (ms) that the node will wait after seeing
|
||||||
|
// an announced transaction before explicitly requesting it
|
||||||
|
TxArrivalWait time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server manages all peer connections.
|
// Server manages all peer connections.
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ gcmode = "archive"
|
||||||
# netrestrict = ""
|
# netrestrict = ""
|
||||||
# nodekey = ""
|
# nodekey = ""
|
||||||
# nodekeyhex = ""
|
# nodekeyhex = ""
|
||||||
|
# txarrivalwait = "500ms"
|
||||||
# [p2p.discovery]
|
# [p2p.discovery]
|
||||||
# v5disc = false
|
# v5disc = false
|
||||||
# bootnodes = []
|
# bootnodes = []
|
||||||
|
|
@ -65,6 +66,7 @@ gcmode = "archive"
|
||||||
# etherbase = ""
|
# etherbase = ""
|
||||||
# extradata = ""
|
# extradata = ""
|
||||||
# recommit = "2m5s"
|
# recommit = "2m5s"
|
||||||
|
# commitinterrupt = true
|
||||||
|
|
||||||
[jsonrpc]
|
[jsonrpc]
|
||||||
ipcpath = "/var/lib/bor/bor.ipc"
|
ipcpath = "/var/lib/bor/bor.ipc"
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ syncmode = "full"
|
||||||
# netrestrict = ""
|
# netrestrict = ""
|
||||||
# nodekey = ""
|
# nodekey = ""
|
||||||
# nodekeyhex = ""
|
# nodekeyhex = ""
|
||||||
|
# txarrivalwait = "500ms"
|
||||||
# [p2p.discovery]
|
# [p2p.discovery]
|
||||||
# v5disc = false
|
# v5disc = false
|
||||||
# bootnodes = []
|
# bootnodes = []
|
||||||
|
|
@ -65,6 +66,7 @@ syncmode = "full"
|
||||||
# etherbase = ""
|
# etherbase = ""
|
||||||
# extradata = ""
|
# extradata = ""
|
||||||
# recommit = "2m5s"
|
# recommit = "2m5s"
|
||||||
|
# commitinterrupt = true
|
||||||
|
|
||||||
[jsonrpc]
|
[jsonrpc]
|
||||||
ipcpath = "/var/lib/bor/bor.ipc"
|
ipcpath = "/var/lib/bor/bor.ipc"
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ syncmode = "full"
|
||||||
# netrestrict = ""
|
# netrestrict = ""
|
||||||
# nodekey = ""
|
# nodekey = ""
|
||||||
# nodekeyhex = ""
|
# nodekeyhex = ""
|
||||||
|
# txarrivalwait = "500ms"
|
||||||
# [p2p.discovery]
|
# [p2p.discovery]
|
||||||
# v5disc = false
|
# v5disc = false
|
||||||
# bootnodes = []
|
# bootnodes = []
|
||||||
|
|
@ -67,6 +68,7 @@ syncmode = "full"
|
||||||
# etherbase = ""
|
# etherbase = ""
|
||||||
# extradata = ""
|
# extradata = ""
|
||||||
# recommit = "2m5s"
|
# recommit = "2m5s"
|
||||||
|
# commitinterrupt = true
|
||||||
|
|
||||||
[jsonrpc]
|
[jsonrpc]
|
||||||
ipcpath = "/var/lib/bor/bor.ipc"
|
ipcpath = "/var/lib/bor/bor.ipc"
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ syncmode = "full"
|
||||||
# netrestrict = ""
|
# netrestrict = ""
|
||||||
# nodekey = ""
|
# nodekey = ""
|
||||||
# nodekeyhex = ""
|
# nodekeyhex = ""
|
||||||
|
# txarrivalwait = "500ms"
|
||||||
# [p2p.discovery]
|
# [p2p.discovery]
|
||||||
# v5disc = false
|
# v5disc = false
|
||||||
# bootnodes = []
|
# bootnodes = []
|
||||||
|
|
@ -67,6 +68,7 @@ syncmode = "full"
|
||||||
# etherbase = ""
|
# etherbase = ""
|
||||||
# extradata = ""
|
# extradata = ""
|
||||||
# recommit = "2m5s"
|
# recommit = "2m5s"
|
||||||
|
# commitinterrupt = true
|
||||||
|
|
||||||
[jsonrpc]
|
[jsonrpc]
|
||||||
ipcpath = "/var/lib/bor/bor.ipc"
|
ipcpath = "/var/lib/bor/bor.ipc"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
Source: bor
|
Source: bor
|
||||||
Version: 0.3.9-stable
|
Version: 0.4.0
|
||||||
Section: develop
|
Section: develop
|
||||||
Priority: standard
|
Priority: standard
|
||||||
Maintainer: Polygon <release-team@polygon.technology>
|
Maintainer: Polygon <release-team@polygon.technology>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue