diff --git a/.circleci/config.yml b/.circleci/config.yml index bcac607de9..67e15d75d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,20 +1,104 @@ -# Golang CircleCI 2.0 configuration file -# Check https://circleci.com/docs/2.0/language-go/ for more details +orbs: + aws-cli: circleci/aws-cli@1.0.0 #See: https://circleci.com/orbs/registry/orb/circleci/aws-cli -version: 2.1 - -executors: - golang: - docker: - - image: circleci/golang:1.13 - working_directory: /go/src/github.com/ethereum/go-ethereum +version: 2.1 jobs: - build: - executor: golang + test: + docker: + - image: cimg/go:1.22.0 + steps: + - checkout + - run: + name: Prep env + command: | + mkdir -p /home/circleci/go/src + mkdir artifacts + go mod tidy + - run: + name: test rpc + command: go test ./rpc/ + - run: + name: test eth + command: go test ./eth/ + - run: + name: test eth/tracers + command: go test ./eth/tracers/ + - run: + name: test core + command: go test ./core/ + - run: + name: test core/vm + command: go test ./core/vm/ + - run: + name: test core/state + command: go test ./core/state/ + - run: + name: test core/rawdb + command: go test ./core/rawdb/ + build_geth_push: + docker: # run the steps with Docker + - image: cimg/go:1.22.0 # ...with this image as the primary container + # this is where all `steps` will run steps: - - checkout # check out source code to working directory + - checkout + - setup_remote_docker - run: - command: make all + name: Prep env + command: | + mkdir -p /home/circleci/go/src + mkdir artifacts + go mod tidy - run: - command: make test + name: build geth binaries + command: | + sudo apt update + sudo apt install gcc-aarch64-linux-gnu libc6-dev-arm64-cross wget -y + export GOPATH=$HOME/go + export GOARCH=amd64 + go build -o ./artifacts/bor-linux-amd64-${CIRCLE_TAG} ./cmd/cli + CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOARCH=arm64 go build -o ./artifacts/bor-linux-arm64-${CIRCLE_TAG} ./cmd/cli + - run: + name: "Publish Release on GitHub" + command: | + go install github.com/tcnksm/ghr@v0.14.0 + NAME=plugeth2-${CIRCLE_TAG} + VERSION=${CIRCLE_TAG} + ghr -draft -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -name $NAME -delete ${VERSION} ./artifacts/ + - aws-cli/setup: #See: https://circleci.com/orbs/registry/orb/circleci/aws-cli + aws-access-key-id: ACCESS_KEY + aws-secret-access-key: SECRET_ACCESS_KEY + aws-region: AWS_REGION + - run: + name: push to s3 + command: | + aws s3 cp ./artifacts/bor-linux-amd64-${CIRCLE_TAG} s3://ethercattle-binaries/plugeth2/$CIRCLE_TAG/bor-linux-amd64 --acl=public-read + aws s3 cp ./artifacts/bor-linux-arm64-${CIRCLE_TAG} s3://ethercattle-binaries/plugeth2/$CIRCLE_TAG/bor-linux-arm64 --acl=public-read + - run: + name: Message Slack + command: | + ./slack-post.sh -w $SLACK_WEBHOOK -m "*plugeth-bor2*:\nTag: $CIRCLE_TAG \n" + +workflows: + version: 2 + test: + jobs: + - test: + filters: + tags: + ignore: /^v.*/ + build_and_test: + jobs: + - test: + filters: + tags: + only: /^v.*/ + branches: + ignore: /.*/ + - build_geth_push: + context: Rivet + requires: + - test + filters: + tags: + only: /^v.*/ diff --git a/.gitignore b/.gitignore index 7405ea2675..d1b43f1eb3 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ dist logs/ tests/spec-tests/ + +# xplugeth_imports +/internal/cli/server/xplugeth_hooks.go diff --git a/README.md b/README.md index 4ad99c2dcd..3d5a51e8d3 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,60 @@ -# Bor Overview -Bor is the Official Golang implementation of the Polygon PoS blockchain. It is a fork of [geth](https://github.com/ethereum/go-ethereum) and is EVM compatible (upto London fork). +# PluGeth -[![API Reference]( -https://pkg.go.dev/badge/github.com/maticnetwork/bor -)](https://pkg.go.dev/github.com/maticnetwork/bor) -[![Go Report Card](https://goreportcard.com/badge/github.com/maticnetwork/bor)](https://goreportcard.com/report/github.com/maticnetwork/bor) -![MIT License](https://img.shields.io/github/license/maticnetwork/bor) -[![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.com/invite/0xpolygonrnd) -[![Twitter Follow](https://img.shields.io/twitter/follow/0xPolygon.svg?style=social)](https://twitter.com/0xPolygon) +PluGeth is a fork of the [Go Ethereum Client](https://github.com/ethereum/go-ethereum) +(Geth) that implements a plugin architecture, allowing developers to extend +Geth's capabilities in a number of different ways using plugins, rather than +having to create additional, new forks of Geth. -### Installing bor using packaging +Documentation can be found [here](https://plugeth.org). -The easiest way to get started with bor is to install the packages using the command below. Refer to the [releases](https://github.com/maticnetwork/bor/releases) section to find the latest stable version of bor. - - curl -L https://raw.githubusercontent.com/maticnetwork/install/main/bor.sh | bash -s -- v0.4.0 +## Design Goals -The network accepts `mainnet`,`amoy` or `mumbai` and the node type accepts `validator` or `sentry` or `archive`. The installation script does the following things: -- Create a new user named `bor`. -- Install the bor binary at `/usr/bin/bor`. -- Dump the suitable config file (based on the network and node type provided) at `/var/lib/bor` and uses it as the home dir. -- Create a systemd service named `bor` at `/lib/systemd/system/bor.service` which starts bor using the config file as `bor` user. +The upstream Geth client exists primarily to serve as a client for the Ethereum +mainnet, though it also supports a number of popular testnets. Supporting the +Ethereum mainnet is a big enough challenge in its own right that the Geth team +generally avoids changes to support other networks, or to provide features only +a small handful of users would be interested in. -The releases supports both the networks i.e. Polygon Mainnet, Amoy and Mumbai (Testnet) unless explicitly specified. Before the stable release for mainnet, pre-releases will be available marked with `beta` tag for deploying on Mumbai/Amoy (testnet). On sufficient testing, stable release for mainnet will be announced with a forum post. +The result is that many projects have forked Geth. Some implement their own +consensus protocols or alter the behavior of the EVM to support other networks. +Others are designed to extract information from the Ethereum mainnet in ways +the standard Geth client does not support. -### Building from source +PluGeth aims to provide a single Geth fork that developers can choose to extend +rather than forking the Geth project. Out of the box, PluGeth behaves exactly +like upstream Geth, but by installing plugins written in Golang, developers can +extend its functionality in a wide variety of way. -- Install Go (version 1.19 or later) and a C compiler. -- Clone the repository and build the binary using the following commands: - ```shell - make bor - ``` -- Start bor using the ideal config files for validator and sentry provided in the `packaging` folder. - ```shell - ./build/bin/bor server --config ./packaging/templates/mainnet-v1/sentry/sentry/bor/config.toml - ``` -- To build full set of utilities, run: - ```shell - make all - ``` -- Run unit and integration tests - ```shell - make test && make test-integration - ``` +### Submitting Pull Requests -#### Using the new cli +We are eager to include contributions from the community into the project. We ask that pull requests which include new features to be covered by our test plugin found in: `/plugins/test-plugin`. The test design and instructions for use are documented there. If further assistance is needed please get in touch with us. -Post `v0.3.0` release, bor uses a new command line interface (cli). The new-cli (located at `internal/cli`) has been built with keeping the flag usage similar to old-cli (located at `cmd/geth`) with a few notable changes. Please refer to [docs](./docs) section for flag usage guide and example. +### Contact Us -### Documentation +If you're trying to do something that isn't supported by the current plugin system, Reach out to us on [Discord](https://discord.gg/Epf7b7Gr) and we'll help you figure out how to make it work. -- The official documentation for the Polygon PoS chain can be found [here](https://wiki.polygon.technology/docs/pos/getting-started/). It contains all the conceptual and architectural details of the chain along with operational guide for users running the nodes. -- New release announcements and discussions can be found on our [forum page](https://forum.polygon.technology/). -- Polygon improvement proposals can be found [here](https://github.com/maticnetwork/Polygon-Improvement-Proposals/) +## System Requirements -### Contribution guidelines +System requirements will vary depending on which network you are connecting to. +On the Ethereum mainnet, you should have at least 8 GB RAM, 2 CPUs, and 350 GB +of SSD disks. -Thank you for considering helping out with the source code! We welcome contributions from anyone on the internet, and are grateful for even the smallest of fixes! If you'd like to contribute to bor, please fork, fix, commit and send a pull request for the maintainers to review and merge into the main code base. +PluGeth relies on Golang's Plugin implementation, which is only supported on +Linux, FreeBSD, and macOS. Windows support is unlikely to be added in the +foreseeable future. -From the outset we defined some guidelines to ensure new contributions only ever enhance the project: +# Licensing Considerations -* Quality: Code in the Polygon project should meet the style guidelines, with sufficient test-cases, descriptive commit messages, evidence that the contribution does not break any compatibility commitments or cause adverse feature interactions, and evidence of high-quality peer-review. Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). -* Testing: Please ensure that the updated code passes all the tests locally before submitting a pull request. In order to run unit tests, run `make test` and to run integration tests, run `make test-integration`. -* Size: The Polygon project’s culture is one of small pull-requests, regularly submitted. The larger a pull-request, the more likely it is that you will be asked to resubmit as a series of self-contained and individually reviewable smaller PRs. -* Maintainability: If the feature will require ongoing maintenance (e.g. support for a particular brand of database), we may ask you to accept responsibility for maintaining this feature -* Pull requests need to be based on and opened against the `develop` branch. -* PR title should be prefixed with package(s) they modify. - * E.g. "eth, rpc: make trace configs optional" +The Geth codebase is licensed under the LGPL. By linking with Geth, you have an +obligation to enable anyone you provide your plugin binaries to run against +their own modified versions of Geth. Because of how Golang plugins work +running against updated versions of Geth may require recompiling the plugin. -## License +If you plan to license your plugin under the LGPL or a more permissive license, +you should be able to meet these requirements. If you plan to use your plugin +privately without distributing it, you should be fine. If you plan to release +your plugin without making the source available, you may find yourself in +violation of Geth's license unless you can provide a way to relink it against +more recent versions of Geth. -The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the -[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), -also included in our repository in the `COPYING.LESSER` file. -The go-ethereum binaries (i.e. all code inside of the `cmd` directory) are licensed under the -[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also -included in our repository in the `COPYING` file. - -## Join our Discord server - -Join Polygon community – share your ideas or just say hi over on [Polygon Community Discord](https://discord.com/invite/0xPolygonCommunity) or on [Polygon R&D Discord](https://discord.com/invite/0xpolygonrnd). diff --git a/SECURITY.md b/SECURITY.md index d082838a32..e89ae8e512 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,14 +1,9 @@ -# Polygon Technology Security Information +# Security Policy -## Link to vulnerability disclosure details (Bug Bounty) -- Websites and Applications: https://hackerone.com/polygon-technology -- Smart Contracts: https://immunefi.com/bounty/polygon -## Languages that our team speaks and understands. -Preferred-Languages: en -## Security-related job openings at Polygon. -https://polygon.technology/careers +Please see [Releases](https://github.com/openrelayxyz/plugeth/releases) for notes on each release. We recommend using the most recently released version. -## Polygon security contact details -security@polygon.technology +To report security issues in the underlying Geth code code please see Geth's security policy [here](https://github.com/ethereum/go-ethereum/security/policy). + +To report PluGeth specific issues please email [security@rivet.cloud](mailto:security@rivet.cloud). diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 39ff5d83c6..0000000000 --- a/circle.yml +++ /dev/null @@ -1,32 +0,0 @@ -machine: - services: - - docker - -dependencies: - cache_directories: - - "~/.ethash" # Cache the ethash DAG generated by hive for consecutive builds - - "~/.docker" # Cache all docker images manually to avoid lengthy rebuilds - override: - # Restore all previously cached docker images - - mkdir -p ~/.docker - - for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done - - # Pull in and hive, restore cached ethash DAGs and do a dry run - - go get -u github.com/karalabe/hive - - (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash) - - (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/) - - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6) - - # Cache all the docker images and the ethash DAGs - - for img in `docker images | grep -v "^" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done - - cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash - -test: - override: - # Build Geth and move into a known folder - - make geth - - cp ./build/bin/geth $HOME/geth - - # Run hive and move all generated logs into the public artifacts folder - - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=go-ethereum:local --override=$HOME/geth --test=. --sim=.) - - cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS diff --git a/core/blockchain.go b/core/blockchain.go index 3097ad4367..aaf1e2f856 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1776,6 +1776,10 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { // writeBlockWithState writes block, metadata and corresponding state data to the // database. func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, statedb *state.StateDB) ([]*types.Log, error) { + //begin PluGeth injection + var interval time.Duration + _ = pluginSetTrieFlushIntervalClone(interval) // this is being called here to engage a testing scenario + //end PluGeth injection // Calculate the total difficulty of the block ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1) if ptd == nil { @@ -1869,6 +1873,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. chosen := current - state.TriesInMemory flushInterval := time.Duration(bc.flushInterval.Load()) // If we exceeded time allowance, flush an entire trie to disk + + // begin PluGeth code injection + flushInterval = pluginSetTrieFlushIntervalClone(flushInterval) + // end PluGeth code injection + if bc.gcproc > flushInterval { // If the header is missing (canonical chain behind), we're reorging a low // diff sidechain. Suspend committing until this operation is completed. @@ -1943,7 +1952,17 @@ func (bc *BlockChain) writeBlockAndSetHead(ctx context.Context, block *types.Blo if status == CanonStatTy { bc.writeHeadBlock(block) } + //begin PluGeth code injection + ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1) + if ptd == nil { + return NonStatTy, consensus.ErrUnknownAncestor + } + externTd := new(big.Int).Add(block.Difficulty(), ptd) + // end PluGeth code injection if status == CanonStatTy { + // begin plugeth injection + pluginNewHead(block, block.Hash(), logs, externTd) + // end plugeth injection bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) if len(logs) > 0 { @@ -1969,6 +1988,9 @@ func (bc *BlockChain) writeBlockAndSetHead(ctx context.Context, block *types.Blo // BOR } } else { + //begin PluGeth injection + pluginNewSideBlock(block, block.Hash(), logs) + //end PluGeth injection bc.chainSideFeed.Send(ChainSideEvent{Block: block}) bc.chain2HeadFeed.Send(Chain2HeadEvent{ @@ -2904,7 +2926,9 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { msg = "Large chain reorg detected" logFn = log.Warn } - + //begin PluGeth code injection + pluginReorg(commonBlock, oldChain, newChain) + //end PluGeth code injection logFn(msg, "number", commonBlock.Number(), "hash", commonBlock.Hash(), "drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash()) blockReorgAddMeter.Mark(int64(len(newChain))) @@ -3138,6 +3162,14 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { bc.logsFeed.Send(logs) } + // begin PluGeth code injection + ptd := bc.GetTd(head.ParentHash(), head.NumberU64()-1) + externTd := ptd + if ptd != nil { + externTd = new(big.Int).Add(head.Difficulty(), ptd) + } + pluginNewHead(head, head.Hash(), logs, externTd) + // end PluGeth code injection bc.chainHeadFeed.Send(ChainHeadEvent{Block: head}) context := []interface{}{ diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 493bc3c618..1ca415b5fc 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -286,6 +286,9 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize if err != nil { return 0, err } + //begin PluGeth code injection + pluginCommitUpdate(item) + //end PluGeth code injection f.frozen.Store(item) return writeSize, nil } diff --git a/core/rawdb/freezer_batch.go b/core/rawdb/freezer_batch.go index fe7ae614af..1484032274 100644 --- a/core/rawdb/freezer_batch.go +++ b/core/rawdb/freezer_batch.go @@ -45,11 +45,17 @@ func newFreezerBatch(f *Freezer) *freezerBatch { // Append adds an RLP-encoded item of the given kind. func (batch *freezerBatch) Append(kind string, num uint64, item interface{}) error { + // begin PluGeth injection + PluginTrackUpdate(num, kind, item) + // end PluGeth injection return batch.tables[kind].Append(num, item) } // AppendRaw adds an item of the given kind. func (batch *freezerBatch) AppendRaw(kind string, num uint64, item []byte) error { + // begin PluGeth injection + PluginTrackUpdate(num, kind, item) + // end PluGeth injection return batch.tables[kind].AppendRaw(num, item) } diff --git a/core/rawdb/xplugeth_hooks.go b/core/rawdb/xplugeth_hooks.go new file mode 100644 index 0000000000..c4624fc90a --- /dev/null +++ b/core/rawdb/xplugeth_hooks.go @@ -0,0 +1,84 @@ +package rawdb + +import ( + "sync" + "reflect" + + "github.com/openrelayxyz/xplugeth" + "github.com/openrelayxyz/xplugeth/hooks/modifyancients" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + +) + +var ( + freezerUpdates map[uint64]map[string]interface{} + lock sync.Mutex +) + +func PluginTrackUpdate(num uint64, kind string, value interface{}) { + + lock.Lock() + defer lock.Unlock() + if freezerUpdates == nil { + freezerUpdates = make(map[uint64]map[string]interface{}) + } + update, ok := freezerUpdates[num] + if !ok { + update = make(map[string]interface{}) + freezerUpdates[num] = update + } + update[kind] = value +} + +func pluginCommitUpdate(num uint64) { + + lock.Lock() + defer lock.Unlock() + if freezerUpdates == nil { + freezerUpdates = make(map[uint64]map[string]interface{}) + } + min := ^uint64(0) + for i := range freezerUpdates { + if min > i { + min = i + } + } + + for i := min; i < num; i++ { + update, ok := freezerUpdates[i] + defer func(i uint64) { delete(freezerUpdates, i) }(i) + if !ok { + log.Warn("Attempting to commit untracked block", "num", i) + continue + } + if len(xplugeth.GetModules[modifyancients.ModifyAncientsPlugin]()) > 0 { + var keys []string + for k := range update { + keys = append(keys, k) + } + if headeri, ok := update[ChainFreezerHeaderTable]; ok { + var h types.Header + switch v := headeri.(type) { + case []byte: + err := rlp.DecodeBytes(v, &h) + if err != nil { + log.Error("error decoding header, commit update", "err", err) + continue + } + case *types.Header: + log.Error("second case") + h = *v + default: + log.Error("header of unknown type", "type", reflect.TypeOf(headeri)) + continue + } + for _, m := range xplugeth.GetModules[modifyancients.ModifyAncientsPlugin]() { + m.ModifyAncients(num, &h) + } + } + } + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go index e477651906..56d5479737 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -206,7 +206,6 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) } - return sdb, nil } @@ -1661,6 +1660,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) { return nodes.Merge(set) } ) + // Given that some accounts could be destroyed and then recreated within // the same block, account deletions must be processed first. This ensures // that the storage trie nodes deleted during destruction and recreated @@ -1767,6 +1767,19 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU if err != nil { return nil, err } + + // begin PluGeth injection + codeUpdates := make(map[common.Hash][]byte) + for _, code := range ret.codes { + if len(code.blob) > 0 { + // Empty code may or may not be included in this list by Geth based on + // factors I'm not certain of, so to normalize our pending batches, + // exclude empty blobs. + codeUpdates[code.hash] = code.blob + } + } + // end PluGeth injection + // Commit dirty contract code if any exists if db := s.db.DiskDB(); db != nil && len(ret.codes) > 0 { batch := db.NewBatch() @@ -1780,8 +1793,11 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU if !ret.empty() { // If snapshotting is enabled, update the snapshot tree with this new version if s.snap != nil { + //begin PluGeth code injection + pluginStateUpdate(ret.root, ret.originRoot, s.snap, s.trie, ret.destructs, ret.accounts, ret.storages, codeUpdates) + //end PluGeth code injection s.snap = nil - + start := time.Now() if err := s.snaps.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil { log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err) diff --git a/core/state/xplugeth_hooks.go b/core/state/xplugeth_hooks.go new file mode 100644 index 0000000000..0b84fcbfdf --- /dev/null +++ b/core/state/xplugeth_hooks.go @@ -0,0 +1,167 @@ +package state + +import ( + "bytes" + "time" + + "github.com/openrelayxyz/xplugeth" + "github.com/openrelayxyz/xplugeth/hooks/stateupdates" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + acctCheckTimer = metrics.NewRegisteredTimer("plugeth/statedb/accounts/checks", nil) +) + +type acctChecker struct { + snap snapshot.Snapshot + trie Trie +} + +type acctByHasher interface { + GetAccountByHash(common.Hash) (*types.StateAccount, error) +} + +func (ac *acctChecker) exists(k common.Hash) bool { + if hadStorage, ok := ac.snapExists(k); ok { + return hadStorage + } + return ac.trieExists(k) +} + +func (ac *acctChecker) snapExists(k common.Hash) (bool, bool) { + acct, err := ac.snap.AccountRLP(k) + if err != nil { + return false, false + } + return len(acct) != 0, true +} + +func (ac *acctChecker) trieExists(k common.Hash) bool { + trie, ok := ac.trie.(acctByHasher) + if !ok { + log.Warn("Couldn't check trie existence, wrong trie type") + return true + } + acct, _ := trie.GetAccountByHash(k) + return acct != nil +} + +func (ac *acctChecker) updated(k common.Hash, v []byte) bool { + if updated, ok := ac.snapUpdated(k, v); ok { + return updated + } + return ac.trieUpdated(k, v) +} + +func pluginStateUpdate(blockRoot, parentRoot common.Hash, snap snapshot.Snapshot, trie Trie, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte, codeUpdates map[common.Hash][]byte) { + checker := &acctChecker{snap, trie} + + filteredDestructs := make(map[common.Hash]struct{}) + for k, v := range destructs { + if _, ok := accounts[k]; ok { + if !checker.hadStorage(k) { + // If an account is in both destructs and accounts, that means it was + // "destroyed" and recreated in the same block. Especially post-cancun, + // that generally means that an account that had ETH but no code got + // replaced by an account that had code. + // + // If there's data in the accounts map, we only need to process this + // account if there are storage slots we need to clear out, so we + // check the account storage for the empty root. If it's empty, we can + // skip this destruct. We need this check to normalize parallel blocks + // with serial blocks, because they report destructs differently. + continue + } + } else { + if !checker.exists(k) { + // If we have a destruct for an account that didn't exist at the previous + // block, we don't need to destruct it. This normalizes the use case of + // CREATE and CREATE2 deployments to empty addresses that revert. + continue + } + } + filteredDestructs[k] = v + } + + filteredAccounts := make(map[common.Hash][]byte) + start := time.Now() + for k, v := range accounts { + if checker.updated(k, v) { + filteredAccounts[k] = v + } + } + acctCheckTimer.UpdateSince(start) + + for _, m := range xplugeth.GetModules[stateupdates.StateUpdatePlugin]() { + m.StateUpdate(blockRoot, parentRoot, filteredDestructs, filteredAccounts, storage, codeUpdates) + } +} + +func (ac *acctChecker) hadStorage(k common.Hash) bool { + if hadStorage, ok := ac.snapHadStorage(k); ok { + return hadStorage + } + return ac.trieHadStorage(k) +} + +func (ac *acctChecker) trieHadStorage(k common.Hash) bool { + trie, ok := ac.trie.(acctByHasher) + if !ok { + log.Warn("Couldn't check trie updates, wrong trie type") + return true + } + acct, err := trie.GetAccountByHash(k) + if err != nil { + return true + } + if acct == nil { + // No account existed at this hash, so it wouldn't have had storage + return false + } + return acct.Root != types.EmptyRootHash +} + +func (ac *acctChecker) snapHadStorage(k common.Hash) (bool, bool) { + acct, err := ac.snap.Account(k) + if err != nil { + return false, false + } + if acct == nil { + // No account existed at this hash, so it wouldn't have had storage + return false, true + } + if len(acct.Root) > 0 && !bytes.Equal(acct.Root, types.EmptyRootHash.Bytes()) { + return true, true + } + return false, true +} + +func (ac *acctChecker) snapUpdated(k common.Hash, v []byte) (bool, bool) { + acct, err := ac.snap.AccountRLP(k) + if err != nil { + return false, false + } + if len(acct) == 0 { + return false, false + } + return !bytes.Equal(acct, v), true +} + +func (ac *acctChecker) trieUpdated(k common.Hash, v []byte) bool { + trie, ok := ac.trie.(acctByHasher) + if !ok { + log.Warn("Couldn't check trie updates, wrong trie type") + return true + } + oAcct, err := trie.GetAccountByHash(k) + if err != nil { + return true + } + return !bytes.Equal(v, types.SlimAccountRLP(*oAcct)) +} diff --git a/core/xplugeth_hooks.go b/core/xplugeth_hooks.go new file mode 100644 index 0000000000..99339825ad --- /dev/null +++ b/core/xplugeth_hooks.go @@ -0,0 +1,52 @@ +package core + +import ( + "math/big" + "sync" + "time" + + "github.com/openrelayxyz/xplugeth" + "github.com/openrelayxyz/xplugeth/hooks/blockchain" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +func pluginNewHead(block *types.Block, hash common.Hash, logs []*types.Log, td *big.Int) { + for _, m := range xplugeth.GetModules[blockchain.NewHeadPlugin]() { + m.NewHead(block, hash, logs, td) + } +} + +func pluginNewSideBlock(block *types.Block, hash common.Hash, logs []*types.Log) { + for _, m := range xplugeth.GetModules[blockchain.NewSideBlockPlugin]() { + m.NewSideBlock(block, hash, logs) + } +} + +func pluginReorg(commonBlock *types.Block, oldChain, newChain types.Blocks) { + oldChainHashes := make([]common.Hash, len(oldChain)) + for i, block := range oldChain { + oldChainHashes[i] = block.Hash() + } + newChainHashes := make([]common.Hash, len(newChain)) + for i, block := range newChain { + newChainHashes[i] = block.Hash() + } + for _, m := range xplugeth.GetModules[blockchain.ReorgPlugin]() { + m.Reorg(commonBlock.Hash(), oldChainHashes, newChainHashes) + } +} + +func pluginSetTrieFlushIntervalClone(flushInterval time.Duration) time.Duration { + m := xplugeth.GetModules[blockchain.SetTrieFlushIntervalClonePlugin]() + var snc sync.Once + if len(m) > 1 { + snc.Do(func() { log.Warn("The blockChain flushInterval value is being accessed by multiple plugins") }) + } + for _, m := range m { + flushInterval = m.SetTrieFlushIntervalClone(flushInterval) + } + return flushInterval +} diff --git a/go.mod b/go.mod index 616a299f3c..60a11877d1 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/ethereum/go-ethereum go 1.22 -toolchain go1.22.1 - require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 github.com/BurntSushi/toml v1.4.0 @@ -72,6 +70,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/naoina/toml v0.1.1 github.com/olekukonko/tablewriter v0.0.5 + github.com/openrelayxyz/xplugeth v0.1.0 github.com/pelletier/go-toml v1.9.5 github.com/peterh/liner v1.2.2 github.com/protolambda/bls12-381-util v0.1.0 @@ -213,6 +212,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redsync/redsync/v4 v4.0.4 // indirect + github.com/go-yaml/yaml v2.1.0+incompatible // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect diff --git a/go.sum b/go.sum index ce93e54d7f..45247959cf 100644 --- a/go.sum +++ b/go.sum @@ -1263,6 +1263,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4 github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= @@ -1884,6 +1886,8 @@ github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJK github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/openrelayxyz/xplugeth v0.1.0 h1:HSrX0NMUcXUKHDATytsNCp+dkWyBH/dlBw9Jh74+A+w= +github.com/openrelayxyz/xplugeth v0.1.0/go.mod h1:OLIeeTdTo1NJ5JKqzclNoqn+O5wqKf/Z6zWdCF6cFG8= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= diff --git a/internal/cli/server/server.go b/internal/cli/server/server.go index 4d87d64608..c4f333a13a 100644 --- a/internal/cli/server/server.go +++ b/internal/cli/server/server.go @@ -41,6 +41,9 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" + "github.com/openrelayxyz/xplugeth" + xtypes "github.com/openrelayxyz/xplugeth/types" + // Force-load the tracer engines to trigger registration _ "github.com/ethereum/go-ethereum/eth/tracers/js" _ "github.com/ethereum/go-ethereum/eth/tracers/native" @@ -132,6 +135,13 @@ func NewServer(config *Config, opts ...serverOption) (*Server, error) { } } + //begin xplugeth injection + if !disablePlugins() { + xplugeth.Initialize(pluginsConfig()) + log.Info("xplugeth initialized") + } + //end xplugeth injection + // load the chain genesis if err = config.loadChain(); err != nil { return nil, err @@ -284,11 +294,23 @@ func NewServer(config *Config, opts ...serverOption) (*Server, error) { // Set the node instance srv.node = stack + // begin xplugeth injection + xplugeth.StoreSingleton[*node.Node](stack) + xplugeth.StoreSingleton[xtypes.Backend](srv.backend.APIBackend) + pluginInitializeNode() + stack.RegisterAPIs(pluginGetAPIs()) + // end xplugeth injection + // start the node if err := srv.node.Start(); err != nil { return nil, err } + //begin xplugeth injection + pluginBlockchain() + //end xplugeth injection + + return srv, nil } @@ -307,6 +329,10 @@ func (s *Server) Stop() { log.Error("Failed to shutdown open telemetry tracer") } } + + //begin xplugeth injection + defer pluginOnShutdown() + //end xplugeth injection } func (s *Server) setupMetrics(config *TelemetryConfig, serviceName string) error { diff --git a/internal/cli/server/xplugeth_hooks.go b/internal/cli/server/xplugeth_hooks.go new file mode 100644 index 0000000000..7e97e9bdc4 --- /dev/null +++ b/internal/cli/server/xplugeth_hooks.go @@ -0,0 +1,97 @@ +package server + +import ( + "os" + "path/filepath" + "strings" + + "github.com/openrelayxyz/xplugeth" + "github.com/openrelayxyz/xplugeth/types" + "github.com/openrelayxyz/xplugeth/hooks/apis" + "github.com/openrelayxyz/xplugeth/hooks/initialize" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" +) + +func isValidPath(path string) bool { + cleanPath := filepath.Clean(path) + + fileInfo, err := os.Stat(cleanPath) + if err != nil { + return false + } + return fileInfo.IsDir() +} + +func disablePlugins() bool { + val := os.Getenv("DISABLE_PLUGINS") + if len(val) > 0 { + truthyValues := []string{"true", "1", "yes", "y"} + for _, truthy := range truthyValues { + if strings.EqualFold(val, truthy) { + return true + } + } + } + return false +} + + +func pluginsConfig() string { + pluginsConfigEnv := os.Getenv("PLUGINS_CONFIG") + + if pluginsConfigEnv != "" && isValidPath(pluginsConfigEnv) { + log.Info("plugins config path provided", "path", pluginsConfigEnv) + return pluginsConfigEnv + } + + log.Info("plugins config path not provided or invalid") + return "" +} + +func pluginInitializeNode() { + stack, ok := xplugeth.GetSingleton[*node.Node]() + if !ok { + panic("*node.Node singleton not set, xplugeth InitializeNode") + } + backend, ok := xplugeth.GetSingleton[types.Backend]() + if !ok { + panic("types.Backend singleton not set, xplugeth Initializenode") + } + for _, init := range xplugeth.GetModules[initialize.Initializer]() { + init.InitializeNode(stack, backend) + } +} + +func pluginGetAPIs() []rpc.API { + result := []rpc.API{} + + stack, ok := xplugeth.GetSingleton[*node.Node]() + if !ok { + panic("*node.Node singleton not set, xplugeth GetAPIs") + } + backend, ok := xplugeth.GetSingleton[types.Backend]() + if !ok { + panic("types.Backend singleton not set xplugeth GetAPIs") + } + + for _, a := range xplugeth.GetModules[apis.GetAPIs]() { + result = append(result, a.GetAPIs(stack, backend)...) + } + + return result +} + +func pluginOnShutdown() { + for _, shutdown := range xplugeth.GetModules[initialize.Shutdown]() { + shutdown.Shutdown() + } +} + +func pluginBlockchain() { + for _, b := range xplugeth.GetModules[initialize.Blockchain]() { + b.Blockchain() + } +} diff --git a/internal/cli/server/xplugeth_imports.go b/internal/cli/server/xplugeth_imports.go new file mode 100644 index 0000000000..e0dc7a60a1 --- /dev/null +++ b/internal/cli/server/xplugeth_imports.go @@ -0,0 +1,7 @@ +package server + +// +build example_plugin + +import ( + _ "github.com/openrelayxyz/xplugeth/plugins/example" +) \ No newline at end of file diff --git a/rpc/service.go b/rpc/service.go index e5bb228873..c8d3509262 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -68,6 +68,10 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr) } + // begin PluGeth code injection + pluginExtendedCallbacks(callbacks, rcvrVal) + // end PluGeth code injection + r.mu.Lock() defer r.mu.Unlock() diff --git a/rpc/xplugeth_subscription.go b/rpc/xplugeth_subscription.go new file mode 100644 index 0000000000..f426a746c0 --- /dev/null +++ b/rpc/xplugeth_subscription.go @@ -0,0 +1,147 @@ +package rpc + +import ( + "context" + "reflect" + "github.com/ethereum/go-ethereum/log" +) + +// Is t context.Context or *context.Context? +func isContextType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t == contextType +} + +func isChanType(t reflect.Type) bool { + // Pointers to channels are weird, but whatever + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // Make sure we have a channel + if t.Kind() != reflect.Chan { + return false + } + // Make sure it is a receivable channel + return (t.ChanDir() & reflect.RecvDir) == reflect.RecvDir +} + +func isChanPubsub(methodType reflect.Type) bool { + if methodType.NumIn() < 2 || methodType.NumOut() != 2 { + return false + } + return isContextType(methodType.In(1)) && + isChanType(methodType.Out(0)) && + isErrorType(methodType.Out(1)) +} + +func callbackifyChanPubSub(receiver, fn reflect.Value) *callback { + c := &callback{rcvr: receiver, errPos: 1, isSubscribe: true} + fntype := fn.Type() + // Skip receiver and context.Context parameter (if present). + firstArg := 0 + if c.rcvr.IsValid() { + firstArg++ + } + if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType { + c.hasCtx = true + firstArg++ + } + // Add all remaining parameters. + c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg) + for i := firstArg; i < fntype.NumIn(); i++ { + c.argTypes[i-firstArg] = fntype.In(i) + } + + retFnType := reflect.FuncOf(append([]reflect.Type{receiver.Type(), contextType}, c.argTypes...), []reflect.Type{reflect.PointerTo(subscriptionType), errorType}, false) + +// // What follows uses reflection to construct a dynamically typed function equivalent to: +// func(receiver , cctx context.Context, args ...) (*rpc.Subscription, error) { +// notifier, supported := NotifierFromContext(cctx) +// if !supported { return Subscription{}, ErrNotificationsUnsupported} +// ctx, cancel := context.WithCancel(context.Background()) +// ch, err := fn() +// if err != nil { return Subscription{}, err } +// rpcSub := notifier.CreateSubscription() +// go func() { +// select { +// case v, ok := <- ch: +// if !ok { return } +// notifier.Notify(rpcSub.ID, v) +// case <-rpcSub.Err(): +// cancel() +// return +// case <-notifier.Closed(): +// cancel() +// return +// } +// }() +// return rpcSub, nil +// } +// + + c.fn = reflect.MakeFunc(retFnType, func(args []reflect.Value) ([]reflect.Value) { + notifier, supported := NotifierFromContext(args[1].Interface().(context.Context)) + if !supported { + return []reflect.Value{reflect.Zero(subscriptionType), reflect.ValueOf(ErrNotificationsUnsupported)} + } + ctx, cancel := context.WithCancel(context.Background()) + + args[1] = reflect.ValueOf(ctx) + out := fn.Call(args) + if !out[1].IsNil() { + // This amounts to: if err != nil { return nil, err } + cancel() + return []reflect.Value{reflect.Zero(subscriptionType), out[1]} + } + // Geth's provided context is done once we've returned the subscription id. + // This new context will cancel when the notifier closes. + + rpcSub := notifier.CreateSubscription() + go func() { + defer cancel() + defer log.Info("Plugin subscription goroutine closed") + selectCases := []reflect.SelectCase{ + {Dir: reflect.SelectRecv, Chan: out[0]}, + {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(rpcSub.Err())}, + // {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(notifier.Closed())}, + } + for { + chosen, val, recvOK := reflect.Select(selectCases) + switch chosen { + case 0: // val, ok := <-ch + if !recvOK { + return + } + if err := notifier.Notify(rpcSub.ID, val.Interface()); err != nil { + log.Warn("Subscription notification failed", "id", rpcSub.ID, "err", err) + } + case 1: + return + // case 2: + // cancel() + // return + } + } + }() + return []reflect.Value{reflect.ValueOf(rpcSub), reflect.Zero(errorType)} + }) + return c +} + + +func pluginExtendedCallbacks(callbacks map[string]*callback, receiver reflect.Value) { + typ := receiver.Type() + for m := 0; m < typ.NumMethod(); m++ { + method := typ.Method(m) + if method.PkgPath != "" { + continue // method not exported + } + if isChanPubsub(method.Type) { + cb := callbackifyChanPubSub(receiver, method.Func) + name := formatName(method.Name) + callbacks[name] = cb + } + } +} \ No newline at end of file diff --git a/slack-post.sh b/slack-post.sh new file mode 100755 index 0000000000..56f342ee06 --- /dev/null +++ b/slack-post.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +# Usage: slackpost -w -c -u -m [-a ] +# Usage: echo | slackpost -w -c -u [-a ] + +# exit immediately if a command exits with a non-zero status +set -e + +# error if variable referenced before being set +set -u + +# produce failure return code if any command fails in pipe +set -o pipefail + +# accepted values: good, warning, danger +alert_type="" +channel="" +message="" +username="" +webhook_url="" + +# colon after var means it has a value rather than it being a bool flag +while getopts 'a:c:m:u:w:' OPTION; do + case "$OPTION" in + a) + alert_type="$OPTARG" + ;; + c) + channel="$OPTARG" + ;; + m) + message="$OPTARG" + ;; + u) + username="$OPTARG" + ;; + w) + webhook_url="$OPTARG" + ;; + ?) + echo "script usage: $(basename $0) {-c channel} {-m message} {-u username} {-w webhook} [-a alert_type]" >&2 + exit 1 + ;; + esac +done +shift "$(($OPTIND -1))" + +# # exit if channel not provided +# if [[ -z "$channel" ]] +# then +# echo "No channel specified" +# exit 1 +# fi + +# read piped data as message if message argument is not provided +if [[ -z "$message" ]] +then + message=$* + + while IFS= read -r line; do + message="$message$line\n" + done +fi + +# # exit if username not provided +# if [[ -z "$username" ]] +# then +# echo "No username specified" +# exit 1 +# fi + +# exit if webhook not provided +if [[ -z "$webhook_url" ]] +then + echo "No webhook_url specified" + exit 1 +fi + +# escape message text +escapedText=$(echo $message | sed 's/"/\"/g' | sed "s/'/\'/g") + +# create JSON payload +# json="{\"channel\": \"$channel\", \"username\":\"$username\", \"icon_emoji\":\"ghost\", \"attachments\":[{\"color\":\"$alert_type\" , \"text\": \"$escapedText\"}]}" +json="{\"attachments\":[{\"color\":\"$alert_type\" , \"text\": \"$escapedText\"}]}" + +# fire off slack message post +curl -s -d "payload=$json" "$webhook_url" diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go index bb0deca9a7..4575284d65 100644 --- a/triedb/hashdb/database.go +++ b/triedb/hashdb/database.go @@ -414,6 +414,11 @@ func (db *Database) Commit(node common.Hash, report bool) error { // outside code doesn't see an inconsistent state (referenced data removed from // memory cache during commit but not yet in persistent storage). This is ensured // by only uncaching existing data when the database write finalizes. + + // begin PluGeth injection + pluginPreTrieCommit(node) + // end PluGeth injection + start := time.Now() batch := db.diskdb.NewBatch() @@ -452,6 +457,10 @@ func (db *Database) Commit(node common.Hash, report bool) error { db.gcnodes, db.gcsize, db.gctime = 0, 0, 0 db.flushnodes, db.flushsize, db.flushtime = 0, 0, 0 + // begin PluGeth injection + pluginPostTrieCommit(node) + // end PluGeth injection + return nil } diff --git a/triedb/hashdb/xplugeth_hooks.go b/triedb/hashdb/xplugeth_hooks.go new file mode 100644 index 0000000000..046131e985 --- /dev/null +++ b/triedb/hashdb/xplugeth_hooks.go @@ -0,0 +1,20 @@ +package hashdb + +import ( + "github.com/openrelayxyz/xplugeth" + "github.com/openrelayxyz/xplugeth/hooks/triecommit" + + "github.com/ethereum/go-ethereum/common" +) + +func pluginPreTrieCommit(node common.Hash) { + for _, m := range xplugeth.GetModules[triecommit.PreTrieCommit]() { + m.PreTrieCommit(node) + } +} + +func pluginPostTrieCommit(node common.Hash) { + for _, m := range xplugeth.GetModules[triecommit.PostTrieCommit]() { + m.PostTrieCommit(node) + } +}