dev: add: merge develop in upstream-merge

This commit is contained in:
marcello33 2023-06-13 12:37:20 +02:00
commit 1c106dc251
82 changed files with 5528 additions and 583 deletions

View file

@ -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

View file

@ -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

View file

@ -61,116 +61,6 @@ 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: any
- name: Putting toml for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/testnet-v4/sentry/sentry/bor/config.toml packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/var/lib/bor/
env:
ARCH: amd64
NODE: bootnode
NETWORK: mumbai
- name: Copying the preinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/preinst packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/preinst
env:
ARCH: amd64
NODE: bootnode
NETWORK: any
- name: Copying the postinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/postinst.profile packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postinst
env:
ARCH: amd64
NODE: bootnode
NETWORK: any
- name: Copying the prerm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/prerm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/prerm
env:
ARCH: amd64
NODE: bootnode
NETWORK: any
- name: Copying the postrm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/postrm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postrm
env:
ARCH: amd64
NODE: bootnode
NETWORK: any
- name: Copying systemd file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/systemd/bor_bootnode.service packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/lib/systemd/system/
env:
ARCH: amd64
NODE: bootnode
NETWORK: any
- name: Copying profile control file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/control.profile.amd64 packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/control
env:
ARCH: amd64
NODE: bootnode
NETWORK: mumbai
- name: Running package build for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: amd64
NODE: bootnode
NETWORK: mumbai
- name: Prepping ${{ env.NETWORK }} ${{ env.NODE }} bootnode for ${{ env.ARCH }}
run: cp -rp packaging/deb/bor packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Putting toml for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/var/lib/bor/
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Copying the preinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/preinst packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/preinst
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Copying the postinst for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/postinst.profile packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postinst
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Copying the prerm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/prerm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/prerm
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Copying the postrm for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/postrm packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/postrm
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Copying systemd file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/systemd/bor_bootnode.service packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/lib/systemd/system/
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Copying profile control file for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: cp -rp packaging/templates/package_scripts/control.profile.amd64 packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}/DEBIAN/control
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Running package build for ${{ env.NODE }} on ${{ env.NETWORK }} on ${{ env.ARCH }}
run: dpkg-deb --build --root-owner-group packaging/deb/bor-${{ env.NETWORK }}-${{ env.NODE }}-config_${{ env.GIT_TAG }}-${{ env.ARCH }}
env:
ARCH: amd64
NODE: bootnode
NETWORK: mainnet
- name: Prepping ${{ env.NETWORK }} ${{ env.NODE }} node for ${{ env.ARCH }} - 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:
@ -526,116 +416,6 @@ 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:

View file

@ -49,7 +49,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Running govulncheck - name: Running govulncheck
uses: Templum/govulncheck-action@v0.0.8 uses: Templum/govulncheck-action@v0.10.1
continue-on-error: true continue-on-error: true
env: env:
DEBUG: "true" DEBUG: "true"

View file

@ -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

View file

@ -16,6 +16,11 @@
"bor": { "bor": {
"jaipurBlock": 22770000, "jaipurBlock": 22770000,
"delhiBlock": 29638656, "delhiBlock": 29638656,
"parallelUniverseBlock": 0,
"indoreBlock": 36877056,
"stateSyncConfirmationDelay": {
"36877056": 128
},
"period": { "period": {
"0": 2, "0": 2,
"25275000": 5, "25275000": 5,

View file

@ -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)
} }

View file

@ -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)
}

View file

@ -1179,22 +1179,42 @@ 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)
} 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)
}
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 {
@ -1217,7 +1237,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
} }

View file

@ -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
} }

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -44,6 +44,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"
@ -88,6 +89,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)
@ -234,6 +237,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
@ -467,6 +471,94 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
return bc, nil return bc, nil
} }
// NewParallelBlockChain , similar to NewBlockChain, creates a new blockchain object, but with a parallel state processor
// TODO marcello check this func
func NewParallelBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64, checker ethereum.ChainValidator) (*BlockChain, error) {
bc, err := NewBlockChain(db, cacheConfig, chainConfig, engine, vmConfig, shouldPreserve, txLookupLimit, checker)
if err != nil {
return nil, err
}
bc.parallelProcessor = NewParallelStateProcessor(chainConfig, bc, engine)
return bc, nil
}
func (bc *BlockChain) ProcessBlock(block *types.Block, parent *types.Header) (types.Receipts, []*types.Log, uint64, *state.StateDB, error) {
// Process the block using processor and parallelProcessor at the same time, take the one which finishes first, cancel the other, and return the result
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
type Result struct {
receipts types.Receipts
logs []*types.Log
usedGas uint64
err error
statedb *state.StateDB
counter metrics.Counter
}
resultChan := make(chan Result, 2)
processorCount := 0
if bc.parallelProcessor != nil {
parallelStatedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
if err != nil {
return nil, nil, 0, nil, err
}
processorCount++
go func() {
parallelStatedb.StartPrefetcher("chain")
receipts, logs, usedGas, err := bc.parallelProcessor.Process(block, parallelStatedb, bc.vmConfig, ctx)
resultChan <- Result{receipts, logs, usedGas, err, parallelStatedb, blockExecutionParallelCounter}
}()
}
if bc.processor != nil {
statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
if err != nil {
return nil, nil, 0, nil, err
}
processorCount++
go func() {
statedb.StartPrefetcher("chain")
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, ctx)
resultChan <- Result{receipts, logs, usedGas, err, statedb, blockExecutionSerialCounter}
}()
}
result := <-resultChan
if _, ok := result.err.(blockstm.ParallelExecFailedError); ok {
log.Warn("Parallel state processor failed", "err", result.err)
// If the parallel processor failed, we will fallback to the serial processor if enabled
if processorCount == 2 {
result.statedb.StopPrefetcher()
result = <-resultChan
processorCount--
}
}
result.counter.Inc(1)
// Make sure we are not leaking any prefetchers
if processorCount == 2 {
go func() {
second_result := <-resultChan
second_result.statedb.StopPrefetcher()
}()
}
return result.receipts, result.logs, result.usedGas, result.statedb, result.err
}
// empty returns an indicator whether the blockchain is empty. // 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
@ -1933,7 +2025,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
pstart := time.Now() pstart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) receipts, logs, usedGas, statedb, err := bc.ProcessBlock(block, parent)
activeState = statedb
if err != nil { if err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true) followupInterrupt.Store(true)
@ -2769,7 +2862,6 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
return 0, errChainStopped return 0, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
// TODO marcello check headerchain method
_, err := bc.hc.InsertHeaderChain(chain, start, bc.forker) _, err := bc.hc.InsertHeaderChain(chain, start, bc.forker)
return 0, err return 0, err
} }

View file

@ -32,7 +32,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"
@ -158,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
@ -182,6 +179,26 @@ 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()
block := blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash())
blockChainB := makeFakeNonEmptyBlockChain(block, 5, ethash.NewFaker(), db, forkSeed, 5)
if err := testBlockChainImport(blockChainB, blockchain); err == nil {
t.Fatalf("expected error for bad tx")
}
}
// testHeaderChainImport tries to process a chain of header, writing them into // 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 {
@ -2027,7 +2044,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*int(DefaultCacheConfig.TriesInMemory), func(i int, b *BlockGen) { fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*int(DefaultCacheConfig.TriesInMemory), func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2}) b.SetCoinbase(common.Address{2})
if int(b.header.Number.Uint64()) >= mergeBlock { if int(b.header.Number.Uint64()) >= mergeBlock {
// TODO marcello SetPoS everywhere
b.SetPoS() b.SetPoS()
} }
}) })

201
core/blockstm/dag.go Normal file
View 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
View 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)
}

View 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
View 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
}

View 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
View 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)
}

View 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
View 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
}
}

View file

@ -434,6 +434,19 @@ func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine,
return db, blocks return db, blocks
} }
// makeBlockChain creates a deterministic chain of blocks rooted at parent with fake invalid transactions.
func makeFakeNonEmptyBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int, numTx int) []*types.Block {
blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
addr := common.Address{0: byte(seed), 19: byte(i)}
b.SetCoinbase(addr)
for j := 0; j < numTx; j++ {
b.txs = append(b.txs, types.NewTransaction(0, addr, big.NewInt(1000), params.TxGas, nil, nil))
}
})
return blocks
}
type fakeChainReader struct { type fakeChainReader struct {
config *params.ChainConfig config *params.ChainConfig
stateSyncData []*types.StateSyncData stateSyncData []*types.StateSyncData

View file

@ -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,12 +84,18 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash
// Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...] // 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 {
if ref.Number.Uint64() <= n { if ref.Number.Uint64() <= n {
// This situation can happen if we're doing tracing and using // This situation can happen if we're doing tracing and using
// block overrides. // block overrides.
return common.Hash{} return 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)

View 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
}

View file

@ -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
@ -148,6 +149,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 {
@ -156,6 +158,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 { if !ch.prevdestruct {
delete(s.stateObjectsDestruct, ch.prev.address) delete(s.stateObjectsDestruct, ch.prev.address)
} }
@ -170,6 +173,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))
} }
} }
@ -204,6 +208,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 {
@ -212,6 +217,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 {

View file

@ -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"
@ -78,6 +79,14 @@ type StateDB struct {
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block
// Block-stm related fields
mvHashmap *blockstm.MVHashMap
incarnation int
readMap map[blockstm.Key]blockstm.ReadDescriptor
writeMap map[blockstm.Key]blockstm.WriteDescriptor
revertedKeys map[blockstm.Key]struct{}
dep int
// DB error. // 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
@ -144,6 +153,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
stateObjectsPending: make(map[common.Address]struct{}), stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]struct{}), stateObjectsDestruct: 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(),
@ -160,6 +170,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 (s *StateDB) SetMVHashmap(mvhm *blockstm.MVHashMap) {
s.mvHashmap = mvhm
s.dep = -1
}
func (s *StateDB) GetMVHashmap() *blockstm.MVHashMap {
return s.mvHashmap
}
func (s *StateDB) MVWriteList() []blockstm.WriteDescriptor {
writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap))
for _, v := range s.writeMap {
if _, ok := s.revertedKeys[v.Path]; !ok {
writes = append(writes, v)
}
}
return writes
}
func (s *StateDB) MVFullWriteList() []blockstm.WriteDescriptor {
writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap))
for _, v := range s.writeMap {
writes = append(writes, v)
}
return writes
}
func (s *StateDB) MVReadMap() map[blockstm.Key]blockstm.ReadDescriptor {
return s.readMap
}
func (s *StateDB) MVReadList() []blockstm.ReadDescriptor {
reads := make([]blockstm.ReadDescriptor, 0, len(s.readMap))
for _, v := range s.MVReadMap() {
reads = append(reads, v)
}
return reads
}
func (s *StateDB) ensureReadMap() {
if s.readMap == nil {
s.readMap = make(map[blockstm.Key]blockstm.ReadDescriptor)
}
}
func (s *StateDB) ensureWriteMap() {
if s.writeMap == nil {
s.writeMap = make(map[blockstm.Key]blockstm.WriteDescriptor)
}
}
func (s *StateDB) ClearReadMap() {
s.readMap = make(map[blockstm.Key]blockstm.ReadDescriptor)
}
func (s *StateDB) ClearWriteMap() {
s.writeMap = make(map[blockstm.Key]blockstm.WriteDescriptor)
}
func (s *StateDB) HadInvalidRead() bool {
return s.dep >= 0
}
func (s *StateDB) DepTxIndex() int {
return s.dep
}
func (s *StateDB) SetIncarnation(inc int) {
s.incarnation = inc
}
type StorageVal[T any] struct {
Value *T
}
func MVRead[T any](s *StateDB, k blockstm.Key, defaultV T, readStorage func(s *StateDB) T) (v T) {
if s.mvHashmap == nil {
return readStorage(s)
}
s.ensureReadMap()
if s.writeMap != nil {
if _, ok := s.writeMap[k]; ok {
return readStorage(s)
}
}
if !k.IsAddress() {
// If we are reading subpath from a deleted account, return default value instead of reading from MVHashmap
addr := k.GetAddress()
if s.getStateObject(addr) == nil {
return defaultV
}
}
res := s.mvHashmap.Read(k, s.txIndex)
var rd blockstm.ReadDescriptor
rd.V = blockstm.Version{
TxnIndex: res.DepIdx(),
Incarnation: res.Incarnation(),
}
rd.Path = k
switch res.Status() {
case blockstm.MVReadResultDone:
{
v = readStorage(res.Value().(*StateDB))
rd.Kind = blockstm.ReadKindMap
}
case blockstm.MVReadResultDependency:
{
s.dep = res.DepIdx()
panic("Found dependency")
}
case blockstm.MVReadResultNone:
{
v = readStorage(s)
rd.Kind = blockstm.ReadKindStorage
}
default:
return defaultV
}
// TODO: I assume we don't want to overwrite an existing read because this could - for example - change a storage
// read to map if the same value is read multiple times.
if _, ok := s.readMap[k]; !ok {
s.readMap[k] = rd
}
return
}
func MVWrite(s *StateDB, k blockstm.Key) {
if s.mvHashmap != nil {
s.ensureWriteMap()
s.writeMap[k] = blockstm.WriteDescriptor{
Path: k,
V: s.Version(),
Val: s,
}
}
}
func RevertWrite(s *StateDB, k blockstm.Key) {
s.revertedKeys[k] = struct{}{}
}
func MVWritten(s *StateDB, k blockstm.Key) bool {
if s.mvHashmap == nil || s.writeMap == nil {
return false
}
_, ok := s.writeMap[k]
return ok
}
// FlushMVWriteSet applies entries in the write set to MVHashMap. Note that this function does not clear the write set.
func (s *StateDB) FlushMVWriteSet() {
if s.mvHashmap != nil && s.writeMap != nil {
s.mvHashmap.FlushMVWriteSet(s.MVFullWriteList())
}
}
// ApplyMVWriteSet applies entries in a given write set to StateDB. Note that this function does not change MVHashMap nor write set
// of the current StateDB.
func (s *StateDB) ApplyMVWriteSet(writes []blockstm.WriteDescriptor) {
for i := range writes {
path := writes[i].Path
sr := writes[i].Val.(*StateDB)
if path.IsState() {
addr := path.GetAddress()
stateKey := path.GetStateKey()
state := sr.GetState(addr, stateKey)
s.SetState(addr, stateKey, state)
} else if path.IsAddress() {
continue
} else {
addr := path.GetAddress()
switch path.GetSubpath() {
case BalancePath:
s.SetBalance(addr, sr.GetBalance(addr))
case NoncePath:
s.SetNonce(addr, sr.GetNonce(addr))
case CodePath:
s.SetCode(addr, sr.GetCode(addr))
case SuicidePath:
stateObject := sr.getDeletedStateObject(addr)
if stateObject != nil && stateObject.deleted {
s.Suicide(addr)
}
default:
panic(fmt.Errorf("unknown key type: %d", path.GetSubpath()))
}
}
}
}
type DumpStruct struct {
TxIdx int
TxInc int
VerIdx int
VerInc int
Path []byte
Op string
}
// GetReadMapDump gets readMap Dump of format: "TxIdx, Inc, Path, Read"
func (s *StateDB) GetReadMapDump() []DumpStruct {
readList := s.MVReadList()
res := make([]DumpStruct, 0, len(readList))
for _, val := range readList {
temp := &DumpStruct{
TxIdx: s.txIndex,
TxInc: s.incarnation,
VerIdx: val.V.TxnIndex,
VerInc: val.V.Incarnation,
Path: val.Path[:],
Op: "Read\n",
}
res = append(res, *temp)
}
return res
}
// GetWriteMapDump gets writeMap Dump of format: "TxIdx, Inc, Path, Write"
func (s *StateDB) GetWriteMapDump() []DumpStruct {
writeList := s.MVReadList()
res := make([]DumpStruct, 0, len(writeList))
for _, val := range writeList {
temp := &DumpStruct{
TxIdx: s.txIndex,
TxInc: s.incarnation,
VerIdx: val.V.TxnIndex,
VerInc: val.V.Incarnation,
Path: val.Path[:],
Op: "Write\n",
}
res = append(res, *temp)
}
return res
}
// AddEmptyMVHashMap adds empty MVHashMap to StateDB
func (s *StateDB) AddEmptyMVHashMap() {
mvh := blockstm.MakeMVHashMap()
s.mvHashmap = mvh
}
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // 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.
@ -267,22 +552,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.
@ -290,37 +593,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.
@ -354,11 +672,13 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
// GetCommittedState retrieves a value from the given account's committed storage trie. // 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.
@ -382,11 +702,13 @@ func (s *StateDB) StorageTrie(addr common.Address) (Trie, error) {
} }
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
})
} }
/* /*
@ -396,44 +718,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))
} }
} }
@ -462,6 +808,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,
@ -470,6 +818,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
} }
@ -553,15 +904,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)
} }
@ -603,6 +955,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) {
@ -618,6 +971,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) {
@ -637,6 +1012,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
} }
@ -657,22 +1034,23 @@ 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))
} }
} }
func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
so := db.getStateObject(addr) so := s.getStateObject(addr)
if so == nil { if so == nil {
return nil return nil
} }
tr, err := so.getTrie(db.db) tr, err := so.getTrie(s.db)
if err != nil { if err != nil {
return err return err
} }
it := trie.NewIterator(tr.NodeIterator(nil)) it := trie.NewIterator(tr.NodeIterator(nil))
for it.Next() { for it.Next() {
key := common.BytesToHash(db.trie.GetKey(it.Key)) key := common.BytesToHash(s.trie.GetKey(it.Key))
if value, dirty := so.dirtyStorage[key]; dirty { if value, dirty := so.dirtyStorage[key]; dirty {
if !cb(key, value) { if !cb(key, value) {
return nil return nil
@ -705,6 +1083,7 @@ func (s *StateDB) Copy() *StateDB {
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)),
stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)), stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)),
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,
@ -796,6 +1175,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
} }

View file

@ -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"
) )
@ -498,6 +501,457 @@ func TestTouchDelete(t *testing.T) {
} }
} }
func TestMVHashMapReadWriteDelete(t *testing.T) {
t.Parallel()
db := NewDatabase(rawdb.NewMemoryDatabase())
mvhm := blockstm.MakeMVHashMap()
s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
states := []*StateDB{s}
// Create copies of the original state for each transition
for i := 1; i <= 4; i++ {
sCopy := s.Copy()
sCopy.txIndex = i
states = append(states, sCopy)
}
addr := common.HexToAddress("0x01")
key := common.HexToHash("0x01")
val := common.HexToHash("0x01")
balance := new(big.Int).SetUint64(uint64(100))
// Tx0 read
v := states[0].GetState(addr, key)
assert.Equal(t, common.Hash{}, v)
// Tx1 write
states[1].GetOrNewStateObject(addr)
states[1].SetState(addr, key, val)
states[1].SetBalance(addr, balance)
states[1].FlushMVWriteSet()
// Tx1 read
v = states[1].GetState(addr, key)
b := states[1].GetBalance(addr)
assert.Equal(t, val, v)
assert.Equal(t, balance, b)
// Tx2 read
v = states[2].GetState(addr, key)
b = states[2].GetBalance(addr)
assert.Equal(t, val, v)
assert.Equal(t, balance, b)
// Tx3 delete
states[3].Suicide(addr)
// Within Tx 3, the state should not change before finalize
v = states[3].GetState(addr, key)
assert.Equal(t, val, v)
// After finalizing Tx 3, the state will change
states[3].Finalise(false)
v = states[3].GetState(addr, key)
assert.Equal(t, common.Hash{}, v)
states[3].FlushMVWriteSet()
// Tx4 read
v = states[4].GetState(addr, key)
b = states[4].GetBalance(addr)
assert.Equal(t, common.Hash{}, v)
assert.Equal(t, common.Big0, b)
}
func TestMVHashMapRevert(t *testing.T) {
t.Parallel()
db := NewDatabase(rawdb.NewMemoryDatabase())
mvhm := blockstm.MakeMVHashMap()
s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
states := []*StateDB{s}
// Create copies of the original state for each transition
for i := 1; i <= 4; i++ {
sCopy := s.Copy()
sCopy.txIndex = i
states = append(states, sCopy)
}
addr := common.HexToAddress("0x01")
key := common.HexToHash("0x01")
val := common.HexToHash("0x01")
balance := new(big.Int).SetUint64(uint64(100))
// Tx0 write
states[0].GetOrNewStateObject(addr)
states[0].SetState(addr, key, val)
states[0].SetBalance(addr, balance)
states[0].FlushMVWriteSet()
// Tx1 perform some ops and then revert
snapshot := states[1].Snapshot()
states[1].AddBalance(addr, new(big.Int).SetUint64(uint64(100)))
states[1].SetState(addr, key, common.HexToHash("0x02"))
v := states[1].GetState(addr, key)
b := states[1].GetBalance(addr)
assert.Equal(t, new(big.Int).SetUint64(uint64(200)), b)
assert.Equal(t, common.HexToHash("0x02"), v)
states[1].Suicide(addr)
states[1].RevertToSnapshot(snapshot)
v = states[1].GetState(addr, key)
b = states[1].GetBalance(addr)
assert.Equal(t, val, v)
assert.Equal(t, balance, b)
states[1].Finalise(false)
states[1].FlushMVWriteSet()
// Tx2 check the state and balance
v = states[2].GetState(addr, key)
b = states[2].GetBalance(addr)
assert.Equal(t, val, v)
assert.Equal(t, balance, b)
}
func TestMVHashMapMarkEstimate(t *testing.T) {
t.Parallel()
db := NewDatabase(rawdb.NewMemoryDatabase())
mvhm := blockstm.MakeMVHashMap()
s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
states := []*StateDB{s}
// Create copies of the original state for each transition
for i := 1; i <= 4; i++ {
sCopy := s.Copy()
sCopy.txIndex = i
states = append(states, sCopy)
}
addr := common.HexToAddress("0x01")
key := common.HexToHash("0x01")
val := common.HexToHash("0x01")
balance := new(big.Int).SetUint64(uint64(100))
// Tx0 read
v := states[0].GetState(addr, key)
assert.Equal(t, common.Hash{}, v)
// Tx0 write
states[0].SetState(addr, key, val)
v = states[0].GetState(addr, key)
assert.Equal(t, val, v)
states[0].FlushMVWriteSet()
// Tx1 write
states[1].GetOrNewStateObject(addr)
states[1].SetState(addr, key, val)
states[1].SetBalance(addr, balance)
states[1].FlushMVWriteSet()
// Tx2 read
v = states[2].GetState(addr, key)
b := states[2].GetBalance(addr)
assert.Equal(t, val, v)
assert.Equal(t, balance, b)
// Tx1 mark estimate
for _, v := range states[1].MVWriteList() {
mvhm.MarkEstimate(v.Path, 1)
}
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
} else {
t.Log("Recovered in f", r)
}
}()
// Tx2 read again should get default (empty) vals because its dependency Tx1 is marked as estimate
states[2].GetState(addr, key)
states[2].GetBalance(addr)
// Tx1 read again should get Tx0 vals
v = states[1].GetState(addr, key)
assert.Equal(t, val, v)
}
func TestMVHashMapOverwrite(t *testing.T) {
t.Parallel()
db := NewDatabase(rawdb.NewMemoryDatabase())
mvhm := blockstm.MakeMVHashMap()
s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
states := []*StateDB{s}
// Create copies of the original state for each transition
for i := 1; i <= 4; i++ {
sCopy := s.Copy()
sCopy.txIndex = i
states = append(states, sCopy)
}
addr := common.HexToAddress("0x01")
key := common.HexToHash("0x01")
val1 := common.HexToHash("0x01")
balance1 := new(big.Int).SetUint64(uint64(100))
val2 := common.HexToHash("0x02")
balance2 := new(big.Int).SetUint64(uint64(200))
// Tx0 write
states[0].GetOrNewStateObject(addr)
states[0].SetState(addr, key, val1)
states[0].SetBalance(addr, balance1)
states[0].FlushMVWriteSet()
// Tx1 write
states[1].SetState(addr, key, val2)
states[1].SetBalance(addr, balance2)
v := states[1].GetState(addr, key)
b := states[1].GetBalance(addr)
states[1].FlushMVWriteSet()
assert.Equal(t, val2, v)
assert.Equal(t, balance2, b)
// Tx2 read should get Tx1's value
v = states[2].GetState(addr, key)
b = states[2].GetBalance(addr)
assert.Equal(t, val2, v)
assert.Equal(t, balance2, b)
// Tx1 delete
for _, v := range states[1].writeMap {
mvhm.Delete(v.Path, 1)
states[1].writeMap = nil
}
// Tx2 read should get Tx0's value
v = states[2].GetState(addr, key)
b = states[2].GetBalance(addr)
assert.Equal(t, val1, v)
assert.Equal(t, balance1, b)
// Tx1 read should get Tx0's value
v = states[1].GetState(addr, key)
b = states[1].GetBalance(addr)
assert.Equal(t, val1, v)
assert.Equal(t, balance1, b)
// Tx0 delete
for _, v := range states[0].writeMap {
mvhm.Delete(v.Path, 0)
states[0].writeMap = nil
}
// Tx2 read again should get default vals
v = states[2].GetState(addr, key)
b = states[2].GetBalance(addr)
assert.Equal(t, common.Hash{}, v)
assert.Equal(t, common.Big0, b)
}
func TestMVHashMapWriteNoConflict(t *testing.T) {
t.Parallel()
db := NewDatabase(rawdb.NewMemoryDatabase())
mvhm := blockstm.MakeMVHashMap()
s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
states := []*StateDB{s}
// Create copies of the original state for each transition
for i := 1; i <= 4; i++ {
sCopy := s.Copy()
sCopy.txIndex = i
states = append(states, sCopy)
}
addr := common.HexToAddress("0x01")
key1 := common.HexToHash("0x01")
key2 := common.HexToHash("0x02")
val1 := common.HexToHash("0x01")
balance1 := new(big.Int).SetUint64(uint64(100))
val2 := common.HexToHash("0x02")
// Tx0 write
states[0].GetOrNewStateObject(addr)
states[0].FlushMVWriteSet()
// Tx2 write
states[2].SetState(addr, key2, val2)
states[2].FlushMVWriteSet()
// Tx1 write
tx1Snapshot := states[1].Snapshot()
states[1].SetState(addr, key1, val1)
states[1].SetBalance(addr, balance1)
states[1].FlushMVWriteSet()
// Tx1 read
assert.Equal(t, val1, states[1].GetState(addr, key1))
assert.Equal(t, balance1, states[1].GetBalance(addr))
// Tx1 should see empty value in key2
assert.Equal(t, common.Hash{}, states[1].GetState(addr, key2))
// Tx2 read
assert.Equal(t, val2, states[2].GetState(addr, key2))
// Tx2 should see values written by Tx1
assert.Equal(t, val1, states[2].GetState(addr, key1))
assert.Equal(t, balance1, states[2].GetBalance(addr))
// Tx3 read
assert.Equal(t, val1, states[3].GetState(addr, key1))
assert.Equal(t, val2, states[3].GetState(addr, key2))
assert.Equal(t, balance1, states[3].GetBalance(addr))
// Tx2 delete
for _, v := range states[2].writeMap {
mvhm.Delete(v.Path, 2)
states[2].writeMap = nil
}
assert.Equal(t, val1, states[3].GetState(addr, key1))
assert.Equal(t, balance1, states[3].GetBalance(addr))
assert.Equal(t, common.Hash{}, states[3].GetState(addr, key2))
// Tx1 revert
states[1].RevertToSnapshot(tx1Snapshot)
states[1].FlushMVWriteSet()
assert.Equal(t, common.Hash{}, states[3].GetState(addr, key1))
assert.Equal(t, common.Hash{}, states[3].GetState(addr, key2))
assert.Equal(t, common.Big0, states[3].GetBalance(addr))
// Tx1 delete
for _, v := range states[1].writeMap {
mvhm.Delete(v.Path, 1)
states[1].writeMap = nil
}
assert.Equal(t, common.Hash{}, states[3].GetState(addr, key1))
assert.Equal(t, common.Hash{}, states[3].GetState(addr, key2))
assert.Equal(t, common.Big0, states[3].GetBalance(addr))
}
func TestApplyMVWriteSet(t *testing.T) {
t.Parallel()
db := NewDatabase(rawdb.NewMemoryDatabase())
mvhm := blockstm.MakeMVHashMap()
s, _ := NewWithMVHashmap(common.Hash{}, db, nil, mvhm)
sClean := s.Copy()
sClean.mvHashmap = nil
sSingleProcess := sClean.Copy()
states := []*StateDB{s}
// Create copies of the original state for each transition
for i := 1; i <= 4; i++ {
sCopy := s.Copy()
sCopy.txIndex = i
states = append(states, sCopy)
}
addr1 := common.HexToAddress("0x01")
addr2 := common.HexToAddress("0x02")
addr3 := common.HexToAddress("0x03")
key1 := common.HexToHash("0x01")
key2 := common.HexToHash("0x02")
val1 := common.HexToHash("0x01")
balance1 := new(big.Int).SetUint64(uint64(100))
val2 := common.HexToHash("0x02")
balance2 := new(big.Int).SetUint64(uint64(200))
code := []byte{1, 2, 3}
// Tx0 write
states[0].GetOrNewStateObject(addr1)
states[0].SetState(addr1, key1, val1)
states[0].SetBalance(addr1, balance1)
states[0].SetState(addr2, key2, val2)
states[0].GetOrNewStateObject(addr3)
states[0].Finalise(true)
states[0].FlushMVWriteSet()
sSingleProcess.GetOrNewStateObject(addr1)
sSingleProcess.SetState(addr1, key1, val1)
sSingleProcess.SetBalance(addr1, balance1)
sSingleProcess.SetState(addr2, key2, val2)
sSingleProcess.GetOrNewStateObject(addr3)
sClean.ApplyMVWriteSet(states[0].MVWriteList())
assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
// Tx1 write
states[1].SetState(addr1, key2, val2)
states[1].SetBalance(addr1, balance2)
states[1].SetNonce(addr1, 1)
states[1].Finalise(true)
states[1].FlushMVWriteSet()
sSingleProcess.SetState(addr1, key2, val2)
sSingleProcess.SetBalance(addr1, balance2)
sSingleProcess.SetNonce(addr1, 1)
sClean.ApplyMVWriteSet(states[1].MVWriteList())
assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
// Tx2 write
states[2].SetState(addr1, key1, val2)
states[2].SetBalance(addr1, balance2)
states[2].SetNonce(addr1, 2)
states[2].Finalise(true)
states[2].FlushMVWriteSet()
sSingleProcess.SetState(addr1, key1, val2)
sSingleProcess.SetBalance(addr1, balance2)
sSingleProcess.SetNonce(addr1, 2)
sClean.ApplyMVWriteSet(states[2].MVWriteList())
assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
// Tx3 write
states[3].Suicide(addr2)
states[3].SetCode(addr1, code)
states[3].Finalise(true)
states[3].FlushMVWriteSet()
sSingleProcess.Suicide(addr2)
sSingleProcess.SetCode(addr1, code)
sClean.ApplyMVWriteSet(states[3].MVWriteList())
assert.Equal(t, sSingleProcess.IntermediateRoot(true), sClean.IntermediateRoot(true))
}
// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. // 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) {

View file

@ -57,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)
@ -75,12 +75,19 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) 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 := TransactionToMessage(tx, types.MakeSigner(p.config, header.Number), header.BaseFee) msg, err := TransactionToMessage(tx, 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.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, nil) receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, interruptCtx)
if err != nil { 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)
} }
@ -89,6 +96,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
} }
// Fail if Shanghai not enabled and len(withdrawals) is non-zero. // Fail if Shanghai not enabled and len(withdrawals) is non-zero.
withdrawals := block.Withdrawals() withdrawals := block.Withdrawals()
// TODO marcello IsShanghai
if len(withdrawals) > 0 && !p.config.IsShanghai(block.Time()) { if len(withdrawals) > 0 && !p.config.IsShanghai(block.Time()) {
return nil, nil, 0, fmt.Errorf("withdrawals before shanghai") return nil, nil, 0, fmt.Errorf("withdrawals before shanghai")
} }
@ -104,12 +112,51 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
txContext := NewEVMTxContext(msg) 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, interruptCtx)
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 { if result.Err == vm.ErrInterrupt {
return nil, result.Err return nil, result.Err
} }

View file

@ -248,8 +248,12 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
} }
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
) )
defer blockchain.Stop() 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
@ -262,7 +266,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
} { } {
block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) block := GenerateBadBlock(gspec.ToBlock(), 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")
} }
@ -271,6 +275,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
{ {
@ -287,8 +292,12 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
} }
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
) )
defer blockchain.Stop() 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
@ -301,7 +310,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
} { } {
block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) block := GenerateBadBlock(gspec.ToBlock(), 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")
} }
@ -310,6 +319,7 @@ func TestStateProcessorErrors(t *testing.T) {
} }
} }
} }
}
// TODO marcello Shanghai? // TODO marcello Shanghai?
// ErrMaxInitCodeSizeExceeded, for this we need extra Shanghai (EIP-3860) enabled. // ErrMaxInitCodeSizeExceeded, for this we need extra Shanghai (EIP-3860) enabled.
@ -346,10 +356,15 @@ func TestStateProcessorErrors(t *testing.T) {
} }
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil, nil)
parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
smallInitCode = [320]byte{} smallInitCode = [320]byte{}
) )
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
@ -368,7 +383,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
} { } {
block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) block := GenerateBadBlock(genesis, beacon.New(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")
} }
@ -377,6 +392,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

View file

@ -35,6 +35,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
@ -177,6 +181,13 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool, interruptCtx context.C
return NewStateTransition(evm, msg, gp).TransitionDb(interruptCtx) return NewStateTransition(evm, msg, gp).TransitionDb(interruptCtx)
} }
func ApplyMessageNoFeeBurnOrTip(evm *vm.EVM, msg Message, gp *GasPool, interruptCtx context.Context) (*ExecutionResult, error) {
st := NewStateTransition(evm, msg, gp)
st.noFeeBurnAndTip = true
return st.TransitionDb(interruptCtx)
}
// StateTransition represents a state transition. // StateTransition represents a state transition.
// //
// == The State Transitioning Model // == The State Transitioning Model
@ -311,8 +322,10 @@ func (st *StateTransition) preCheck() error {
// nil evm execution result. // nil evm execution result.
func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*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)
var input2 *big.Int
if !st.noFeeBurnAndTip {
input2 := st.state.GetBalance(st.evm.Context.Coinbase) 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
// //
@ -404,11 +417,18 @@ func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*Executio
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 rules.IsLondon { if rules.IsLondon {
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())
@ -427,11 +447,17 @@ func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*Executio
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
} }

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"context"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
@ -49,5 +50,5 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running // 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)
} }

View file

@ -88,6 +88,11 @@ type Header struct {
// ExcessDataGas was added by EIP-4844 and is ignored in legacy headers. // ExcessDataGas was added by EIP-4844 and is ignored in legacy headers.
ExcessDataGas *big.Int `json:"excessDataGas" rlp:"optional"` ExcessDataGas *big.Int `json:"excessDataGas" rlp:"optional"`
// length of TxDependency -> n (n = number of transactions in the block)
// length of TxDependency[i] -> k (k = a whole number)
// k elements in TxDependency[i] -> transaction indexes on which transaction i is dependent on
TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
/* /*
TODO (MariusVanDerWijden) Add this field once needed 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
@ -285,6 +290,15 @@ func CopyHeader(h *Header) *Header {
cpy.WithdrawalsHash = new(common.Hash) cpy.WithdrawalsHash = new(common.Hash)
*cpy.WithdrawalsHash = *h.WithdrawalsHash *cpy.WithdrawalsHash = *h.WithdrawalsHash
} }
if len(h.TxDependency) > 0 {
cpy.TxDependency = make([][]uint64, len(h.TxDependency))
for i, dep := range h.TxDependency {
cpy.TxDependency[i] = make([]uint64, len(dep))
copy(cpy.TxDependency[i], dep)
}
}
return &cpy return &cpy
} }
@ -341,6 +355,7 @@ func (b *Block) TxHash() common.Hash { return b.header.TxHash }
func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash } func (b *Block) 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 {

View file

@ -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

View file

@ -33,6 +33,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
Nonce BlockNonce `json:"nonce"` Nonce BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -53,6 +54,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.Nonce = h.Nonce enc.Nonce = h.Nonce
enc.BaseFee = (*hexutil.Big)(h.BaseFee) enc.BaseFee = (*hexutil.Big)(h.BaseFee)
enc.WithdrawalsHash = h.WithdrawalsHash enc.WithdrawalsHash = h.WithdrawalsHash
enc.TxDependency = h.TxDependency
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -77,6 +79,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
Nonce *BlockNonce `json:"nonce"` Nonce *BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" 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 {
@ -145,5 +148,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.WithdrawalsHash != nil { if dec.WithdrawalsHash != nil {
h.WithdrawalsHash = dec.WithdrawalsHash h.WithdrawalsHash = dec.WithdrawalsHash
} }
if dec.TxDependency != nil {
h.TxDependency = dec.TxDependency
}
return nil return nil
} }

View file

@ -42,7 +42,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBytes(obj.Nonce[:]) w.WriteBytes(obj.Nonce[:])
_tmp1 := obj.BaseFee != nil _tmp1 := obj.BaseFee != nil
_tmp2 := obj.WithdrawalsHash != nil _tmp2 := obj.WithdrawalsHash != nil
if _tmp1 || _tmp2 { _tmp3 := len(obj.TxDependency) > 0
if _tmp1 || _tmp2 || _tmp3 {
if obj.BaseFee == nil { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
} else { } else {
@ -59,6 +60,17 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBytes(obj.WithdrawalsHash[:]) w.WriteBytes(obj.WithdrawalsHash[:])
} }
} }
if _tmp3 {
_tmp3 := w.List()
for _, _tmp4 := range obj.TxDependency {
_tmp5 := w.List()
for _, _tmp6 := range _tmp4 {
w.WriteUint64(_tmp6)
}
w.ListEnd(_tmp5)
}
w.ListEnd(_tmp3)
}
w.ListEnd(_tmp0) w.ListEnd(_tmp0)
return w.Flush() return w.Flush()
} }

View file

@ -52,6 +52,10 @@ type Config struct {
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
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,
@ -265,6 +269,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i
txHash, _ := GetCurrentTxFromContext(interruptCtx) txHash, _ := GetCurrentTxFromContext(interruptCtx)
interruptedTxCache, _ := GetCache(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 // 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) found, _ := interruptedTxCache.Cache.ContainsOrAdd(txHash, true)
if found { if found {
@ -425,6 +433,11 @@ func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnl
case <-interruptCtx.Done(): case <-interruptCtx.Done():
txHash, _ := GetCurrentTxFromContext(interruptCtx) txHash, _ := GetCurrentTxFromContext(interruptCtx)
interruptedTxCache, _ := GetCache(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 // 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) found, _ := interruptedTxCache.Cache.ContainsOrAdd(txHash, true)
log.Info("FOUND", "found", found, "txHash", txHash) log.Info("FOUND", "found", found, "txHash", txHash)

View file

@ -73,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
@ -129,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

View file

@ -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,7 +220,7 @@ 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 before requesting an announced transaction (default: 500ms) - ```txarrivalwait```: Maximum duration to wait for a transaction before explicitly requesting it (defaults to 500ms) (default: 500ms)
### Sealer Options ### Sealer Options
@ -232,6 +236,8 @@ The ```bor server``` command runs the Bor client.
- ```miner.recommit```: The time interval for miner to re-create mining work (default: 2m5s) - ```miner.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)

View file

@ -111,7 +111,6 @@ type Ethereum struct {
// New creates a new Ethereum object (including the // New creates a new Ethereum object (including the
// initialisation of the common Ethereum object) // initialisation of the common Ethereum object)
// TODO fixme marcello c'é qcls che non va qua (CreateConsensusEngine chiamata due volte)
func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Ensure configuration values are compatible and sane // Ensure configuration values are compatible and sane
if config.SyncMode == downloader.LightSync { if config.SyncMode == downloader.LightSync {
@ -208,6 +207,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,
@ -230,7 +231,15 @@ 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 {
// TODO marcello fix NewParallelBlockChain
ethereum.blockchain, err = core.NewParallelBlockChain(chainDb, cacheConfig, config.Genesis, ethereum.engine, vmConfig, ethereum.shouldPreserve, &config.TxLookupLimit, checker)
} else {
ethereum.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, ethereum.engine, vmConfig, ethereum.shouldPreserve, &config.TxLookupLimit, checker) ethereum.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, ethereum.engine, vmConfig, ethereum.shouldPreserve, &config.TxLookupLimit, checker)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -1608,9 +1608,9 @@ func TestFakedSyncProgress66NoRemoteCheckpoint(t *testing.T) {
tester := newTester(t) tester := newTester(t)
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
@ -1626,7 +1626,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

View file

@ -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()

View file

@ -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")

View file

@ -18,7 +18,7 @@
package ethconfig package ethconfig
import ( import (
"github.com/ethereum/go-ethereum/internal/ethapi" "math/big"
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
@ -41,6 +41,7 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
@ -242,6 +243,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"`
// 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"`
} }

View file

@ -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:

View file

@ -159,7 +159,9 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe
if current = eth.blockchain.GetBlockByNumber(next); current == nil { if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, nil, fmt.Errorf("block #%d not found", next) return nil, nil, fmt.Errorf("block #%d not found", next)
} }
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}, nil)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
} }

View file

@ -20,10 +20,14 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"math/big"
"os" "os"
"path/filepath"
"runtime" "runtime"
"sync" "sync"
"time" "time"
@ -68,6 +72,10 @@ const (
// for tracing. The creation of trace state will be paused if the unused // for tracing. The creation of trace state will be paused if the unused
// trace states exceed this limit. // trace states exceed this limit.
maximumPendingTraceStates = 128 maximumPendingTraceStates = 128
defaultPath = string(".")
defaultIOFlag = false
) )
var defaultBorTraceEnabled = newBoolPtr(false) var defaultBorTraceEnabled = newBoolPtr(false)
@ -78,6 +86,8 @@ var errTxNotFound = errors.New("transaction not found")
// historical state for tracing purposes. // historical state for tracing purposes.
type StateReleaseFunc func() type StateReleaseFunc func()
var allowIOTracing = false // Change this to true to enable IO tracing for debugging
// Backend interface provides the common API services (that are provided by // 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 {
@ -204,6 +214,8 @@ type TraceConfig struct {
Tracer *string Tracer *string
Timeout *string Timeout *string
Reexec *uint64 Reexec *uint64
Path *string
IOFlag *bool
// Config specific to given tracer. Note struct logger // Config specific to given tracer. Note struct logger
// config are historically embedded in main object. // config are historically embedded in main object.
TracerConfig json.RawMessage TracerConfig json.RawMessage
@ -691,7 +703,6 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
return roots, nil return roots, nil
} }
// StandardTraceBadBlockToFile dumps the structured logs created during the // StandardTraceBadBlockToFile dumps the structured logs created during the
// execution of EVM against a block pulled from the pool of bad ones to the // execution of EVM against a block pulled from the pool of bad ones to the
// local file system and returns a list of files to the caller. // local file system and returns a list of files to the caller.
@ -734,12 +745,29 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
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, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer release() defer release()
// create and add empty mvHashMap in statedb as StateAtBlock does not have mvHashmap in it.
if ioflag {
statedb.AddEmptyMVHashMap()
}
// Execute all the transaction contained within the block concurrently
var ( var (
txs, stateSyncPresent = api.getAllBlockTransactions(ctx, block) txs, stateSyncPresent = api.getAllBlockTransactions(ctx, block)
blockHash = block.Hash() blockHash = block.Hash()
@ -790,10 +818,30 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
}() }()
} }
var IOdump string
var RWstruct []state.DumpStruct
var london bool
if ioflag {
IOdump = "TransactionIndex, Incarnation, VersionTxIdx, VersionInc, Path, Operation\n"
RWstruct = []state.DumpStruct{}
}
// Feed the transactions into the tracers and return // Feed the transactions into the tracers and return
var failed error var failed error
if ioflag {
london = api.backend.ChainConfig().IsLondon(block.Number())
}
txloop: txloop:
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
task := &txTraceTask{statedb: statedb.Copy(), index: i} task := &txTraceTask{statedb: statedb.Copy(), index: i}
select { select {
@ -807,10 +855,14 @@ txloop:
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(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 txloop break txloop
@ -824,11 +876,68 @@ txloop:
failed = err failed = err
break txloop break txloop
} }
}
// 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.GasLimit), 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()
@ -845,7 +954,6 @@ txloop:
} }
} }
// standardTraceBlockToFile configures a new tracer which uses standard JSON output, // standardTraceBlockToFile configures a new tracer which uses standard JSON output,
// and traces either a full block or an individual transaction. The return value will // and traces either a full block or an individual transaction. The return value will
// be one filename per transaction traced. // be one filename per transaction traced.

View file

@ -460,6 +460,85 @@ func TestTraceBlock(t *testing.T) {
} }
} }
func TestIOdump(t *testing.T) {
t.Parallel()
// Initialize test accounts
accounts := newAccounts(5)
genesis := &core.Genesis{Alloc: core.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
accounts[3].addr: {Balance: big.NewInt(params.Ether)},
accounts[4].addr: {Balance: big.NewInt(params.Ether)},
}}
genBlocks := 1
signer := types.HomesteadSigner{}
api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
// Transfer from account[0] to account[1], account[1] to account[2], account[2] to account[3], account[3] to account[4], account[4] to account[0]
// value: 1000 wei
// fee: 0 wei
for j := 0; j < 5; j++ {
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[(j+1)%5].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[j].key)
b.AddTx(tx)
}
}))
allowIOTracing = true
ioflag := new(bool)
*ioflag = true
var testSuite = []struct {
blockNumber rpc.BlockNumber
config *TraceConfig
want string
expectErr error
}{
// Trace head block
{
config: &TraceConfig{
IOFlag: ioflag,
},
blockNumber: rpc.BlockNumber(genBlocks),
want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}},{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`,
},
}
for i, tc := range testSuite {
result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config)
if tc.expectErr != nil {
if err == nil {
t.Errorf("test %d, want error %v", i, tc.expectErr)
continue
}
if !reflect.DeepEqual(err, tc.expectErr) {
t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err)
}
continue
}
if err != nil {
t.Errorf("test %d, want no error, have %v", i, err)
continue
}
have, err := json.Marshal(result)
if err != nil {
t.Errorf("Error in Marshal: %v", err)
}
want := tc.want
if string(have) != want {
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want)
}
}
}
func TestTracingWithOverrides(t *testing.T) { func TestTracingWithOverrides(t *testing.T) {
t.Parallel() t.Parallel()
// Initialize test accounts // Initialize test accounts

6
go.mod
View file

@ -43,6 +43,8 @@ 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/bloomfilter/v2 v2.0.3 github.com/holiman/bloomfilter/v2 v2.0.3
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c
github.com/huin/goupnp v1.0.3 github.com/huin/goupnp v1.0.3
@ -180,6 +182,10 @@ require (
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 // indirect github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 // indirect
github.com/cosmos/ledger-cosmos-go v0.10.3 // indirect github.com/cosmos/ledger-cosmos-go v0.10.3 // indirect
github.com/cosmos/ledger-go v0.9.2 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
github.com/emirpasic/gods v1.18.1
github.com/etcd-io/bbolt v1.3.3 // indirect github.com/etcd-io/bbolt v1.3.3 // indirect
github.com/go-kit/kit v0.10.0 // indirect github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect

View file

@ -1067,7 +1067,8 @@ func (c *CallResult) Status() Long {
func (b *Block) Call(ctx context.Context, args struct { func (b *Block) Call(ctx context.Context, args struct {
Data ethapi.TransactionArgs Data ethapi.TransactionArgs
}) (*CallResult, error) { }) (*CallResult, error) {
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap()) // TODO marcello check args number
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1131,7 +1132,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.r.backend, args.Data, pendingBlockNr, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap()) result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -30,6 +30,11 @@ var mumbaiTestnet = &Chain{
Bor: &params.BorConfig{ Bor: &params.BorConfig{
JaipurBlock: big.NewInt(22770000), JaipurBlock: big.NewInt(22770000),
DelhiBlock: big.NewInt(29638656), DelhiBlock: big.NewInt(29638656),
ParallelUniverseBlock: big.NewInt(0),
IndoreBlock: big.NewInt(36877056),
StateSyncConfirmationDelay: map[string]uint64{
"36877056": 128,
},
Period: map[string]uint64{ Period: map[string]uint64{
"0": 2, "0": 2,
"25275000": 5, "25275000": 5,

View file

@ -48,7 +48,12 @@
"22640000": "0x70bcA57F4579f58670aB2d18Ef16e02C17553C38" "22640000": "0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
}, },
"jaipurBlock": 22770000, "jaipurBlock": 22770000,
"delhiBlock": 29638656 "delhiBlock": 29638656,
"parallelUniverseBlock": 0,
"indoreBlock": 36877056,
"stateSyncConfirmationDelay": {
"36877056": 128
}
} }
}, },
"nonce": "0x0", "nonce": "0x0",

View file

@ -50,7 +50,12 @@
"22640000":"0x70bcA57F4579f58670aB2d18Ef16e02C17553C38" "22640000":"0x70bcA57F4579f58670aB2d18Ef16e02C17553C38"
}, },
"jaipurBlock":22770000, "jaipurBlock":22770000,
"delhiBlock": 29638656 "delhiBlock": 29638656,
"parallelUniverseBlock": 0,
"indoreBlock": 36877056,
"stateSyncConfirmationDelay": {
"36877056": 128
}
} }
}, },
"nonce":"0x0", "nonce":"0x0",

View file

@ -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"`
@ -311,6 +314,7 @@ 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"` CommitInterruptFlag bool `hcl:"commitinterrupt,optional" toml:"commitinterrupt,optional"`
} }
@ -569,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",
@ -695,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: "",
@ -748,6 +758,10 @@ func DefaultConfig() *Config {
BlockProfileRate: 0, BlockProfileRate: 0,
// CPUProfile: "", // CPUProfile: "",
}, },
ParallelEVM: &ParallelEVMConfig{
Enable: true,
SpeculativeProcesses: 8,
},
} }
} }
@ -1131,6 +1145,8 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (*
n.BorLogs = c.BorLogs n.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 != "" {

View file

@ -904,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",

View file

@ -1053,16 +1053,40 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
} }
} }
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) 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
@ -1146,7 +1170,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 *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) return s.CallWithState(ctx, args, blockNrOrHash, nil, overrides)
}
// CallWithState executes the given transaction on the given state for
// the given block number. Note that as it does an EVM call, fields in
// the underlying state will change. Make sure to handle it outside of
// this function (ideally by sending a copy of state).
//
// Additionally, the caller can specify a batch of contract for fields overriding.
//
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *BlockChainAPI) CallWithState(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride) (hexutil.Bytes, error) {
result, err := DoCall(ctx, s.b, args, blockNrOrHash, state, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1236,7 +1273,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
executable := func(gas uint64) (bool, *core.ExecutionResult, error) { 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

View file

@ -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.

View file

@ -30,6 +30,7 @@ var (
typeCounterTpl = "# TYPE %s counter\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"
) )
@ -65,11 +66,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')
} }
@ -80,7 +86,7 @@ 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()) c.writeCounter(name, m.Count())
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])
@ -94,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')
} }
@ -108,17 +122,27 @@ func (c *collector) writeGaugeCounter(name string, value interface{}) {
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value)) c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
} }
func (c *collector) writeSummaryCounter(name string, value interface{}) { func (c *collector) writeCounter(name string, value interface{}) {
name = mutateKey(name + "_count") name = mutateKey(name + "_count")
c.buff.WriteString(fmt.Sprintf(typeCounterTpl, name)) c.buff.WriteString(fmt.Sprintf(typeCounterTpl, name))
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value)) c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
} }
func (c *collector) writeSummaryCounter(name string, value interface{}) {
name = mutateKey(name + "_count")
c.buff.WriteString(fmt.Sprintf(keyCounterTpl, name, value))
}
func (c *collector) writeSummaryPercentile(name, p string, value interface{}) { func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
name = mutateKey(name) name = mutateKey(name)
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.ReplaceAll(key, "/", "_") return strings.ReplaceAll(key, "/", "_")
} }

View file

@ -74,9 +74,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
@ -84,6 +81,8 @@ 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
@ -99,15 +98,17 @@ 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
# TYPE test_resetting_timer_count counter
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)

View file

@ -224,7 +224,7 @@ var (
// Test accounts // Test accounts
testBankKey, _ = crypto.GenerateKey() testBankKey, _ = crypto.GenerateKey()
TestBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) TestBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
testBankFunds = big.NewInt(8000000000000000000) testBankFunds = big.NewInt(9000000000000000000)
testUserKey, _ = crypto.GenerateKey() testUserKey, _ = crypto.GenerateKey()
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey)

View file

@ -204,7 +204,7 @@ func (b *testWorkerBackend) newRandomTxWithNonce(creation bool, nonce uint64) *t
return tx return tx
} }
// newRandomTxWithGas creates a new transactionto deploy a storage smart contract. // newRandomTxWithGas creates a new transaction to deploy a storage smart contract.
func (b *testWorkerBackend) newStorageCreateContractTx() (*types.Transaction, common.Address) { func (b *testWorkerBackend) newStorageCreateContractTx() (*types.Transaction, common.Address) {
var tx *types.Transaction var tx *types.Transaction
@ -427,28 +427,13 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint, opcodeDelay
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee)) txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
tcount := w.current.tcount tcount := w.current.tcount
var interruptCtx = context.Background() w.commitTransactions(w.current, txset, nil, context.Background())
stopFn := func() {}
defer func() {
stopFn()
}()
if w.interruptCommitFlag {
block := w.chain.GetBlockByHash(w.chain.CurrentBlock().Hash())
interruptCtx, stopFn = getInterruptTimer(ctx, w.current, block)
// nolint : staticcheck
interruptCtx = vm.PutCache(interruptCtx, w.interruptedTxCache)
}
w.commitTransactionsWithDelay(w.current, txset, nil, interruptCtx)
// Only update the snapshot if any new transactions were added // Only update the snapshot if any new transactions were added
// to the pending block // to the pending block
if tcount != w.current.tcount { if tcount != w.current.tcount {
w.updateSnapshot(w.current) w.updateSnapshot(w.current)
} }
stopFn()
} else { } else {
// Special case, if the consensus engine is 0 period clique(dev mode), // Special case, if the consensus engine is 0 period clique(dev mode),
// submit sealing work here since all empty submission will be rejected // submit sealing work here since all empty submission will be rejected
@ -502,7 +487,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
return return
} }
// nolint : contextcheck //nolint:contextcheck
var interruptCtx = context.Background() var interruptCtx = context.Background()
stopFn := func() {} stopFn := func() {}
@ -813,7 +798,6 @@ mainloop:
continue continue
} }
// Start executing the transaction // Start executing the transaction
// TODO marcello check how "Prepare" has been replaced
env.state.SetTxContext(tx.Hash(), env.tcount) env.state.SetTxContext(tx.Hash(), env.tcount)
var start time.Time var start time.Time

View file

@ -43,6 +43,7 @@ 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/core/vm"
@ -988,6 +989,53 @@ 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
var EnableMVHashMap bool
if w.chainConfig.Bor.IsParallelUniverse(env.header.Number) {
EnableMVHashMap = true
} else {
EnableMVHashMap = false
}
// create and add empty mvHashMap in statedb
if EnableMVHashMap {
depsMVReadList = [][]blockstm.ReadDescriptor{}
depsMVFullWriteList = [][]blockstm.WriteDescriptor{}
mvReadMapList = []map[blockstm.Key]blockstm.ReadDescriptor{}
deps = map[int]map[int]bool{}
chDeps = make(chan blockstm.TxDep)
count = 0
depsWg.Add(1)
go func(chDeps chan blockstm.TxDep) {
for t := range chDeps {
deps = blockstm.UpdateDeps(deps, t)
}
depsWg.Done()
}(chDeps)
}
initialGasLimit := env.gasPool.Gas() initialGasLimit := env.gasPool.Gas()
initialTxs := txs.GetTxs() initialTxs := txs.GetTxs()
@ -1007,6 +1055,10 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
mainloop: mainloop:
for { for {
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():
@ -1069,6 +1121,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) {
@ -1081,6 +1149,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.Pop() txs.Pop()
} }
if EnableMVHashMap {
env.state.ClearReadMap()
env.state.ClearWriteMap()
}
}
// nolint:nestif
if EnableMVHashMap {
close(chDeps)
depsWg.Wait()
if len(mvReadMapList) > 0 {
tempDeps := make([][]uint64, len(mvReadMapList))
for j := range deps[0] {
tempDeps[0] = append(tempDeps[0], uint64(j))
}
delayFlag := true
for i := 1; i <= len(mvReadMapList)-1; i++ {
reads := mvReadMapList[i-1]
_, ok1 := reads[blockstm.NewSubpathKey(env.coinbase, state.BalancePath)]
_, ok2 := reads[blockstm.NewSubpathKey(common.HexToAddress(w.chainConfig.Bor.CalculateBurntContract(env.header.Number.Uint64())), state.BalancePath)]
if ok1 || ok2 {
delayFlag = false
}
for j := range deps[i] {
tempDeps[i] = append(tempDeps[i], uint64(j))
}
}
if delayFlag {
env.header.TxDependency = tempDeps
} else {
env.header.TxDependency = nil
}
} else {
env.header.TxDependency = nil
}
} }
if !w.isRunning() && len(coalescedLogs) > 0 { if !w.isRunning() && len(coalescedLogs) > 0 {
// We don't push the pendingLogsEvent while we are sealing. The reason is that // We don't push the pendingLogsEvent while we are sealing. The reason is that
@ -1493,7 +1605,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *atomic.Int32, noempt
return return
} }
// nolint : contextcheck // nolint:contextcheck
var interruptCtx = context.Background() var interruptCtx = context.Background()
stopFn := func() {} stopFn := func() {}

View file

@ -889,11 +889,6 @@ func BenchmarkBorMining(b *testing.B) {
chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, back.Genesis, nil, engine, vm.Config{}, nil, nil, nil) chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, back.Genesis, nil, engine, vm.Config{}, nil, nil, nil)
defer chain.Stop() defer chain.Stop()
// Ignore empty commit here for less noise.
w.skipSealHook = func(task *task) bool {
return len(task.receipts) == 0
}
// fulfill tx pool // fulfill tx pool
const ( const (
totalGas = testGas + params.TxGas totalGas = testGas + params.TxGas
@ -950,3 +945,126 @@ func BenchmarkBorMining(b *testing.B) {
} }
} }
} }
// uses core.NewParallelBlockChain to use the dependencies present in the block header
// params.BorUnittestChainConfig contains the ParallelUniverseBlock ad big.NewInt(5), so the first 4 blocks will not have metadata.
// nolint: gocognit
func BenchmarkBorMiningBlockSTMMetadata(b *testing.B) {
chainConfig := params.BorUnittestChainConfig
ctrl := gomock.NewController(b)
defer ctrl.Finish()
ethAPIMock := api.NewMockCaller(ctrl)
ethAPIMock.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
spanner := bor.NewMockSpanner(ctrl)
spanner.EXPECT().GetCurrentValidatorsByHash(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*valset.Validator{
{
ID: 0,
Address: TestBankAddress,
VotingPower: 100,
ProposerPriority: 0,
},
}, nil).AnyTimes()
heimdallClientMock := mocks.NewMockIHeimdallClient(ctrl)
heimdallClientMock.EXPECT().Close().Times(1)
contractMock := bor.NewMockGenesisContract(ctrl)
db, _, _ := NewDBForFakes(b)
engine := NewFakeBor(b, db, chainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock)
defer engine.Close()
chainConfig.LondonBlock = big.NewInt(0)
w, back, _ := NewTestWorker(b, chainConfig, engine, db, 0, 0, 0, 0)
defer w.close()
// This test chain imports the mined blocks.
db2 := rawdb.NewMemoryDatabase()
back.Genesis.MustCommit(db2)
chain, _ := core.NewParallelBlockChain(db2, nil, back.chain.Config(), engine, vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil)
defer chain.Stop()
// Ignore empty commit here for less noise.
w.skipSealHook = func(task *task) bool {
return len(task.receipts) == 0
}
// fulfill tx pool
const (
totalGas = testGas + params.TxGas
totalBlocks = 10
)
var err error
txInBlock := int(back.Genesis.GasLimit/totalGas) + 1
// a bit risky
for i := 0; i < 2*totalBlocks*txInBlock; i++ {
err = back.txPool.AddLocal(back.newRandomTx(true))
if err != nil {
b.Fatal("while adding a local transaction", err)
}
err = back.txPool.AddLocal(back.newRandomTx(false))
if err != nil {
b.Fatal("while adding a remote transaction", err)
}
}
// Wait for mined blocks.
sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
defer sub.Unsubscribe()
b.ResetTimer()
prev := uint64(time.Now().Unix())
// Start mining!
w.start()
blockPeriod, ok := back.Genesis.Config.Bor.Period["0"]
if !ok {
blockPeriod = 1
}
for i := 0; i < totalBlocks; i++ {
select {
case ev := <-sub.Chan():
block := ev.Data.(core.NewMinedBlockEvent).Block
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
b.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err)
}
// check for dependencies for block number > 4
if block.NumberU64() <= 4 {
if block.TxDependency() != nil {
b.Fatalf("dependency not nil")
}
} else {
deps := block.TxDependency()
if len(deps[0]) != 0 {
b.Fatalf("wrong dependency")
}
for i := 1; i < block.Transactions().Len(); i++ {
if deps[i][0] != uint64(i-1) || len(deps[i]) != 1 {
b.Fatalf("wrong dependency")
}
}
}
b.Log("block", block.NumberU64(), "time", block.Time()-prev, "txs", block.Transactions().Len(), "gasUsed", block.GasUsed(), "gasLimit", block.GasLimit())
prev = block.Time()
case <-time.After(time.Duration(blockPeriod) * time.Second):
b.Fatalf("timeout")
}
}
}

View file

@ -66,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"

View file

@ -66,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"

View file

@ -68,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"

View file

@ -68,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"

View file

@ -1,5 +1,5 @@
Source: bor Source: bor
Version: 0.3.9-beta Version: 0.3.9-stable
Section: develop Section: develop
Priority: standard Priority: standard
Maintainer: Polygon <release-team@polygon.technology> Maintainer: Polygon <release-team@polygon.technology>

View file

@ -1,5 +1,5 @@
Source: bor Source: bor
Version: 0.3.9-beta Version: 0.3.9-stable
Section: develop Section: develop
Priority: standard Priority: standard
Maintainer: Polygon <release-team@polygon.technology> Maintainer: Polygon <release-team@polygon.technology>

View file

@ -1,5 +1,5 @@
Source: bor-profile Source: bor-profile
Version: 0.3.9-beta Version: 0.3.9-stable
Section: develop Section: develop
Priority: standard Priority: standard
Maintainer: Polygon <release-team@polygon.technology> Maintainer: Polygon <release-team@polygon.technology>

View file

@ -1,5 +1,5 @@
Source: bor-profile Source: bor-profile
Version: 0.3.9-beta Version: 0.3.9-stable
Section: develop Section: develop
Priority: standard Priority: standard
Maintainer: Polygon <release-team@polygon.technology> Maintainer: Polygon <release-team@polygon.technology>

View file

@ -1,5 +1,5 @@
Source: bor-profile Source: bor-profile
Version: 0.3.9-beta Version: 0.3.9-stable
Section: develop Section: develop
Priority: standard Priority: standard
Maintainer: Polygon <release-team@polygon.technology> Maintainer: Polygon <release-team@polygon.technology>

View file

@ -1,5 +1,5 @@
Source: bor-profile Source: bor-profile
Version: 0.3.9-beta Version: 0.3.9-stable
Section: develop Section: develop
Priority: standard Priority: standard
Maintainer: Polygon <release-team@polygon.technology> Maintainer: Polygon <release-team@polygon.technology>

View file

@ -66,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"

View file

@ -66,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"

View file

@ -68,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"

View file

@ -68,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"

View file

@ -279,6 +279,7 @@ var (
BerlinBlock: big.NewInt(0), BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
Bor: &BorConfig{ Bor: &BorConfig{
ParallelUniverseBlock: big.NewInt(5),
Period: map[string]uint64{ Period: map[string]uint64{
"0": 1, "0": 1,
}, },
@ -318,6 +319,11 @@ var (
Bor: &BorConfig{ Bor: &BorConfig{
JaipurBlock: big.NewInt(22770000), JaipurBlock: big.NewInt(22770000),
DelhiBlock: big.NewInt(29638656), DelhiBlock: big.NewInt(29638656),
ParallelUniverseBlock: big.NewInt(0),
IndoreBlock: big.NewInt(36877056),
StateSyncConfirmationDelay: map[string]uint64{
"36877056": 128,
},
Period: map[string]uint64{ Period: map[string]uint64{
"0": 2, "0": 2,
"25275000": 5, "25275000": 5,
@ -371,6 +377,7 @@ var (
Bor: &BorConfig{ Bor: &BorConfig{
JaipurBlock: big.NewInt(23850000), JaipurBlock: big.NewInt(23850000),
DelhiBlock: big.NewInt(38189056), DelhiBlock: big.NewInt(38189056),
ParallelUniverseBlock: big.NewInt(0),
Period: map[string]uint64{ Period: map[string]uint64{
"0": 2, "0": 2,
}, },
@ -670,6 +677,9 @@ type BorConfig struct {
BurntContract map[string]string `json:"burntContract"` // governance contract where the token will be sent to and burnt in london fork BurntContract map[string]string `json:"burntContract"` // governance contract where the token will be sent to and burnt in london fork
JaipurBlock *big.Int `json:"jaipurBlock"` // Jaipur switch block (nil = no fork, 0 = already on jaipur) JaipurBlock *big.Int `json:"jaipurBlock"` // Jaipur switch block (nil = no fork, 0 = already on jaipur)
DelhiBlock *big.Int `json:"delhiBlock"` // Delhi switch block (nil = no fork, 0 = already on delhi) DelhiBlock *big.Int `json:"delhiBlock"` // Delhi switch block (nil = no fork, 0 = already on delhi)
ParallelUniverseBlock *big.Int `json:"parallelUniverseBlock"` // TODO: update all occurrence, change name and finalize number (hardfork for block-stm related changes)
IndoreBlock *big.Int `json:"indoreBlock"` // Indore switch block (nil = no fork, 0 = already on indore)
StateSyncConfirmationDelay map[string]uint64 `json:"stateSyncConfirmationDelay"` // StateSync Confirmation Delay, in seconds, to calculate `to`
} }
// String implements the stringer interface, returning the consensus engine details. // String implements the stringer interface, returning the consensus engine details.
@ -678,11 +688,11 @@ func (b *BorConfig) String() string {
} }
func (c *BorConfig) CalculateProducerDelay(number uint64) uint64 { func (c *BorConfig) CalculateProducerDelay(number uint64) uint64 {
return c.calculateSprintSizeHelper(c.ProducerDelay, number) return borKeyValueConfigHelper(c.ProducerDelay, number)
} }
func (c *BorConfig) CalculateSprint(number uint64) uint64 { func (c *BorConfig) CalculateSprint(number uint64) uint64 {
return c.calculateSprintSizeHelper(c.Sprint, number) return borKeyValueConfigHelper(c.Sprint, number)
} }
func (c *BorConfig) CalculateBackupMultiplier(number uint64) uint64 { func (c *BorConfig) CalculateBackupMultiplier(number uint64) uint64 {
@ -701,6 +711,23 @@ func (c *BorConfig) IsDelhi(number *big.Int) bool {
return isBlockForked(c.DelhiBlock, number) return isBlockForked(c.DelhiBlock, number)
} }
func (c *BorConfig) IsIndore(number *big.Int) bool {
return isForked(c.IndoreBlock, number)
}
func (c *BorConfig) CalculateStateSyncDelay(number uint64) uint64 {
return borKeyValueConfigHelper(c.StateSyncConfirmationDelay, number)
}
// TODO: modify this function once the block number is finalized
func (c *BorConfig) IsParallelUniverse(number *big.Int) bool {
if c.ParallelUniverseBlock == big.NewInt(0) {
return false
}
return isForked(c.ParallelUniverseBlock, number)
}
func (c *BorConfig) IsSprintStart(number uint64) bool { func (c *BorConfig) IsSprintStart(number uint64) bool {
return number%c.CalculateSprint(number) == 0 return number%c.CalculateSprint(number) == 0
} }
@ -725,7 +752,7 @@ func (c *BorConfig) calculateBorConfigHelper(field map[string]uint64, number uin
return field[keys[len(keys)-1]] return field[keys[len(keys)-1]]
} }
func (c *BorConfig) calculateSprintSizeHelper(field map[string]uint64, number uint64) uint64 { func borKeyValueConfigHelper(field map[string]uint64, number uint64) uint64 {
keys := make([]string, 0, len(field)) keys := make([]string, 0, len(field))
for k := range field { for k := range field {
keys = append(keys, k) keys = append(keys, k)

View file

@ -24,7 +24,7 @@ const (
VersionMajor = 0 // Major version component of the current release VersionMajor = 0 // Major version component of the current release
VersionMinor = 3 // Minor version component of the current release VersionMinor = 3 // Minor version component of the current release
VersionPatch = 9 // Patch version component of the current release VersionPatch = 9 // Patch version component of the current release
VersionMeta = "beta" // Version metadata to append to the version string VersionMeta = "stable" // Version metadata to append to the version string
) )
var GitCommit string var GitCommit string
@ -43,7 +43,7 @@ var VersionWithMeta = func() string {
return v return v
}() }()
// VersionWithMetaCommitDetails holds the textual version string including the metadata and Git Details. // VersionWithCommitDetails holds the textual version string including the metadata and Git Details.
var VersionWithMetaCommitDetails = func() string { var VersionWithMetaCommitDetails = func() string {
v := Version v := Version
if VersionMeta != "" { if VersionMeta != "" {

81
scripts/updateVersion.sh Executable file
View file

@ -0,0 +1,81 @@
#!/bin/bash
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
echo "The version is of form - VersionMajor.VersionMinor.VersionPatch-VersionMeta"
echo "Let's take 0.3.4-beta as an example. Here:"
echo "* VersionMajor is - 0"
echo "* VersionMinor is - 3"
echo "* VersionPatch is - 4"
echo "* VersionMeta is - beta"
echo ""
echo "Now, enter the new version step-by-step below:"
version=""
# VersionMajor
read -p "* VersionMajor: " VersionMajor
if [ -z "$VersionMajor" ]
then
echo "VersionMajor cannot be NULL"
exit -1
fi
version+=$VersionMajor
# VersionMinor
read -p "* VersionMinor: " VersionMinor
if [ -z "$VersionMinor" ]
then
echo "VersionMinor cannot be NULL"
exit -1
fi
version+="."$VersionMinor
# VersionPatch
read -p "* VersionPatch: " VersionPatch
if [ -z "$VersionPatch" ]
then
echo "VersionPatch cannot be NULL"
exit -1
fi
version+="."$VersionPatch
# VersionMeta (optional)
read -p "* VersionMeta (optional, press enter if not needed): " VersionMeta
if [[ ! -z "$VersionMeta" ]]
then
version+="-"$VersionMeta
fi
echo ""
echo "New version is: $version"
# update version in all the 6 templates
replace="Version: "$version
fileArray=(
"${DIR}/../packaging/templates/package_scripts/control"
"${DIR}/../packaging/templates/package_scripts/control.arm64"
"${DIR}/../packaging/templates/package_scripts/control.profile.amd64"
"${DIR}/../packaging/templates/package_scripts/control.profile.arm64"
"${DIR}/../packaging/templates/package_scripts/control.validator"
"${DIR}/../packaging/templates/package_scripts/control.validator.arm64"
)
for file in ${fileArray[@]}; do
# get the line starting with `Version` in the control file and store it in the $temp variable
temp=$(grep "^Version.*" $file)
sed -i '' "s%$temp%$replace%" $file
done
# update version in ../params/version.go
versionFile="${DIR}/../params/version.go"
sed -i '' "s% = .*// Major% = $VersionMajor // Major%g" $versionFile
sed -i '' "s% = .*// Minor% = $VersionMinor // Minor%g" $versionFile
sed -i '' "s% = .*// Patch% = $VersionPatch // Patch%g" $versionFile
sed -i '' "s% = .*// Version metadata% = \"$VersionMeta\" // Version metadata%g" $versionFile
gofmt -w $versionFile
echo ""
echo "Updating Version Done"
exit 0

View file

@ -544,7 +544,7 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
insertNewBlock(t, chain, block) insertNewBlock(t, chain, block)
} }
lastStateID, _ := _bor.GenesisContractsClient.LastStateId(sprintSize) lastStateID, _ := _bor.GenesisContractsClient.LastStateId(nil, sprintSize, block.Hash())
// state 6 was not written // state 6 was not written
require.Equal(t, uint64(4), lastStateID.Uint64()) require.Equal(t, uint64(4), lastStateID.Uint64())
@ -573,7 +573,7 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
insertNewBlock(t, chain, block) insertNewBlock(t, chain, block)
} }
lastStateID, _ = _bor.GenesisContractsClient.LastStateId(spanSize) lastStateID, _ = _bor.GenesisContractsClient.LastStateId(nil, spanSize, block.Hash())
require.Equal(t, uint64(6), lastStateID.Uint64()) require.Equal(t, uint64(6), lastStateID.Uint64())
} }