mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
pgeth
This commit is contained in:
parent
78a3c32ef4
commit
afb48d4edc
15 changed files with 477 additions and 4 deletions
|
|
@ -3,3 +3,6 @@
|
|||
build/_workspace
|
||||
build/_bin
|
||||
tests/testdata
|
||||
|
||||
plugins/**/
|
||||
!plugins/build.sh
|
||||
|
|
|
|||
55
.github/workflows/sync-and-build-plugins.yaml
vendored
Normal file
55
.github/workflows/sync-and-build-plugins.yaml
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
name: sync
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "pgeth"
|
||||
schedule:
|
||||
- cron: "0 * * * *" # every hour
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: "prepare sync operations"
|
||||
run: |
|
||||
git remote add geth https://github.com/ethereum/go-ethereum
|
||||
git checkout master
|
||||
git fetch geth master
|
||||
- name: "check if diff exists"
|
||||
id: diffcheck
|
||||
run: echo "stdout=$(git diff master geth/master -- | wc -c)" >> $GITHUB_OUTPUT
|
||||
- name: "perform sync operations"
|
||||
if: steps.diffcheck.outputs.stdout != '0'
|
||||
run: |
|
||||
git reset --hard geth/master
|
||||
git checkout pgeth
|
||||
git config --global user.email "iulian@rotaru.fr"
|
||||
git config --global user.name "mortimr"
|
||||
git rebase --stat geth/master
|
||||
- name: "build the docker image to ensure project still builds"
|
||||
if: steps.diffcheck.outputs.stdout != '0'
|
||||
run: docker build . --tag ghcr.io/kilnfi/pgeth:latest --tag ghcr.io/kilnfi/pgeth:${GITHUB_SHA}
|
||||
- name: "push rebased master branch"
|
||||
if: steps.diffcheck.outputs.stdout != '0'
|
||||
run: git push origin master --force
|
||||
- name: "push rebased pgeth branch"
|
||||
if: steps.diffcheck.outputs.stdout != '0'
|
||||
run: git push origin pgeth --force
|
||||
- name: "login to ghcr"
|
||||
if: steps.diffcheck.outputs.stdout != '0'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: "push to ghcr"
|
||||
if: steps.diffcheck.outputs.stdout != '0'
|
||||
run: |
|
||||
docker push ghcr.io/kilnfi/pgeth:latest
|
||||
docker push ghcr.io/kilnfi/pgeth:${GITHUB_SHA}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -50,3 +50,7 @@ profile.cov
|
|||
logs/
|
||||
|
||||
tests/spec-tests/
|
||||
#pgeth
|
||||
|
||||
!plugin/.gitkeep
|
||||
plugins/*/
|
||||
|
|
|
|||
47
Dockerfile.plugins
Normal file
47
Dockerfile.plugins
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Support setting various labels on the final image
|
||||
ARG COMMIT=""
|
||||
ARG VERSION=""
|
||||
ARG BUILDNUM=""
|
||||
|
||||
# Build Geth in a stock Go builder container
|
||||
FROM golang:1.21-alpine as builder
|
||||
|
||||
ARG PLUGIN_REPOSITORIES=""
|
||||
|
||||
RUN apk add --no-cache gcc musl-dev linux-headers git bash
|
||||
|
||||
# Get dependencies - will also be cached if we won't change go.mod/go.sum
|
||||
COPY go.mod /go-ethereum/
|
||||
COPY go.sum /go-ethereum/
|
||||
RUN cd /go-ethereum && go mod download
|
||||
|
||||
ADD . /go-ethereum
|
||||
RUN cd /go-ethereum
|
||||
|
||||
ENV PLUGIN_REPOSITORIES=${PLUGIN_REPOSITORIES}
|
||||
|
||||
RUN env
|
||||
|
||||
RUN cd /go-ethereum && /go-ethereum/pull_plugins.sh
|
||||
|
||||
RUN cd /go-ethereum && /go-ethereum/plugins/build.sh
|
||||
RUN cd /go-ethereum && go run build/ci.go install ./cmd/geth
|
||||
|
||||
# Pull Geth into a second stage deploy alpine container
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
||||
COPY --from=builder /go-ethereum/plugins /pgeth-plugins
|
||||
|
||||
ENV PGETH_DIRECTORY="/pgeth-plugins"
|
||||
|
||||
EXPOSE 8545 8546 30303 30303/udp
|
||||
ENTRYPOINT ["geth"]
|
||||
|
||||
# Add some metadata labels to help programatic image consumption
|
||||
ARG COMMIT=""
|
||||
ARG VERSION=""
|
||||
ARG BUILDNUM=""
|
||||
|
||||
LABEL commit="$COMMIT" version="$VERSION" buildnum="$BUILDNUM"
|
||||
7
Makefile
7
Makefile
|
|
@ -2,13 +2,13 @@
|
|||
# with Go source code. If you know what GOPATH is then you probably
|
||||
# don't need to bother with make.
|
||||
|
||||
.PHONY: geth android ios evm all test clean
|
||||
.PHONY: geth android ios evm all test clean plugin
|
||||
|
||||
GOBIN = ./build/bin
|
||||
GO ?= latest
|
||||
GORUN = go run
|
||||
|
||||
geth:
|
||||
geth: plugin
|
||||
$(GORUN) build/ci.go install ./cmd/geth
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||
|
|
@ -36,3 +36,6 @@ devtools:
|
|||
env GOBIN= go install ./cmd/abigen
|
||||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||
|
||||
plugin:
|
||||
plugins/build.sh
|
||||
12
README.md
12
README.md
|
|
@ -1,6 +1,14 @@
|
|||
## Go Ethereum
|
||||
## Pluggable Go Ethereum
|
||||
|
||||
Official Golang execution layer implementation of the Ethereum protocol.
|
||||
Unofficial Golang execution layer implementation of the Ethereum protocol with plugin support
|
||||
|
||||
The goal is to allow anyone to build [go plugins](https://pkg.go.dev/plugin) that follow our standard plugin interface, and run code alongside the node while accessing core geth capabilities.
|
||||
|
||||
Plugins are running in an isolated mode, crashing with a plugin won't stop the node. Of course, playing with internal geth stuff can break your node.
|
||||
|
||||
Example use cases:
|
||||
- create a plugin that performs better evm simulations, extracting traces quickly
|
||||
- create a block builder
|
||||
|
||||
[ {
|
|||
buildTags = append(buildTags, "ckzg")
|
||||
}
|
||||
|
||||
// PGETH: ADD PUREGO BUILD TAG
|
||||
buildTags = append(buildTags, "purego")
|
||||
|
||||
// Configure the build.
|
||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import (
|
|||
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
|
||||
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
|
||||
|
||||
"github.com/ethereum/go-ethereum/pgeth"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -339,6 +340,11 @@ func geth(ctx *cli.Context) error {
|
|||
defer stack.Close()
|
||||
|
||||
startNode(ctx, stack, backend, false)
|
||||
pe := pgeth.NewEngine(&pgeth.PluginEngineConfig{
|
||||
Node: stack,
|
||||
Backend: backend,
|
||||
})
|
||||
pe.Start(ctx.Context)
|
||||
stack.Wait()
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/internal/version"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/pgeth"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -85,6 +86,13 @@ func printVersion(ctx *cli.Context) error {
|
|||
fmt.Println("Operating System:", runtime.GOOS)
|
||||
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
|
||||
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
|
||||
|
||||
pe := pgeth.NewEngine(&pgeth.PluginEngineConfig{
|
||||
Node: nil,
|
||||
Backend: nil,
|
||||
})
|
||||
pe.Version(ctx.Context)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
27
eth/api_backend.pgeth.go
Normal file
27
eth/api_backend.pgeth.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// 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 eth
|
||||
|
||||
import "github.com/ethereum/go-ethereum/core"
|
||||
|
||||
func (b *EthAPIBackend) Ethereum() *Ethereum {
|
||||
return b.eth
|
||||
}
|
||||
|
||||
func (e *Ethereum) Blockchain() *core.BlockChain {
|
||||
return e.blockchain
|
||||
}
|
||||
71
log/format.pgeth.go
Normal file
71
log/format.pgeth.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func PgethFormat(usecolor bool) Format {
|
||||
return FormatFunc(func(r *Record) []byte {
|
||||
var color = 0
|
||||
if usecolor {
|
||||
switch r.Lvl {
|
||||
case LvlCrit:
|
||||
color = 35
|
||||
case LvlError:
|
||||
color = 31
|
||||
case LvlWarn:
|
||||
color = 33
|
||||
case LvlInfo:
|
||||
color = 34
|
||||
case LvlDebug:
|
||||
color = 36
|
||||
case LvlTrace:
|
||||
color = 34
|
||||
}
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
lvl := r.Lvl.AlignedString()
|
||||
if lvl == "INFO " {
|
||||
lvl = "PLUG "
|
||||
}
|
||||
if locationEnabled.Load() {
|
||||
// Log origin printing was requested, format the location path and line number
|
||||
location := fmt.Sprintf("%+v", r.Call)
|
||||
for _, prefix := range locationTrims {
|
||||
location = strings.TrimPrefix(location, prefix)
|
||||
}
|
||||
// Maintain the maximum location length for fancyer alignment
|
||||
align := int(locationLength.Load())
|
||||
if align < len(location) {
|
||||
align = len(location)
|
||||
locationLength.Store(uint32(align))
|
||||
}
|
||||
padding := strings.Repeat(" ", align-len(location))
|
||||
|
||||
// Assemble and print the log heading
|
||||
if color > 0 {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg)
|
||||
}
|
||||
} else {
|
||||
if color > 0 {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg)
|
||||
}
|
||||
}
|
||||
// try to justify the log output for short messages
|
||||
length := utf8.RuneCountInString(r.Msg)
|
||||
if len(r.Ctx) > 0 && length < termMsgJust {
|
||||
b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length))
|
||||
}
|
||||
// print the keys logfmt style
|
||||
logfmt(b, r.Ctx, color, true)
|
||||
return b.Bytes()
|
||||
})
|
||||
}
|
||||
206
pgeth/engine.go
Normal file
206
pgeth/engine.go
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
package pgeth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/mattn/go-colorable"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type PluginToolkit struct {
|
||||
Node *node.Node
|
||||
Backend ethapi.Backend
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
type PluginDetails struct {
|
||||
Name string `yaml:"name"`
|
||||
Config map[string]interface{} `yaml:"config"`
|
||||
}
|
||||
|
||||
type Plugin struct {
|
||||
Details PluginDetails
|
||||
Start func(*PluginToolkit, map[string]interface{}, context.Context, chan (error))
|
||||
Version func()
|
||||
}
|
||||
|
||||
type PluginEngineConfig struct {
|
||||
Node *node.Node
|
||||
Backend ethapi.Backend
|
||||
}
|
||||
|
||||
type PluginEngine struct {
|
||||
node *node.Node
|
||||
backend ethapi.Backend
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func NewEngine(cfg *PluginEngineConfig) *PluginEngine {
|
||||
logger := log.New()
|
||||
|
||||
var ostream log.Handler
|
||||
output := colorable.NewColorableStdout()
|
||||
ostream = log.StreamHandler(output, log.PgethFormat(true))
|
||||
logger.SetHandler(ostream)
|
||||
return &PluginEngine{
|
||||
node: cfg.Node,
|
||||
backend: cfg.Backend,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PluginEngine) Version(ctx context.Context) error {
|
||||
pluginDirectory := os.Getenv("PGETH_DIRECTORY")
|
||||
|
||||
if len(pluginDirectory) == 0 {
|
||||
p.logger.Warn("Skipping plugin engine startup: PGETH_DIRECTORY is empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
p.logger.Info("Starting plugin engine")
|
||||
|
||||
files, err := ioutil.ReadDir(pluginDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
plugins := []*Plugin{}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
newPlugin, err := p.loadPluginDetailsFromDirectory(filepath.Join(pluginDirectory, file.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plugins = append(plugins, newPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
errChan := make(chan error)
|
||||
|
||||
for _, plug := range plugins {
|
||||
plug.Version()
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-errChan:
|
||||
p.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PluginEngine) Start(ctx context.Context) error {
|
||||
pluginDirectory := os.Getenv("PGETH_DIRECTORY")
|
||||
|
||||
if len(pluginDirectory) == 0 {
|
||||
p.logger.Warn("Skipping plugin engine startup: PGETH_DIRECTORY is empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
p.logger.Info("Starting plugin engine")
|
||||
|
||||
files, err := ioutil.ReadDir(pluginDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
plugins := []*Plugin{}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
newPlugin, err := p.loadPluginDetailsFromDirectory(filepath.Join(pluginDirectory, file.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plugins = append(plugins, newPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
var toolkit *PluginToolkit = &PluginToolkit{
|
||||
Node: p.node,
|
||||
Backend: p.backend,
|
||||
Logger: p.logger,
|
||||
}
|
||||
|
||||
errChan := make(chan error)
|
||||
|
||||
for _, plug := range plugins {
|
||||
go func(_plug *Plugin) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
p.logger.Error("Plugin crashed", "error", err, "plugin", _plug.Details.Name)
|
||||
// logs entire trace
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
_plug.Start(toolkit, _plug.Details.Config, ctx, errChan)
|
||||
}(plug)
|
||||
p.logger.Info(fmt.Sprintf("Starting \"%s\" plugin", plug.Details.Name))
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-errChan:
|
||||
p.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PluginEngine) loadPluginDetailsFromDirectory(pluginDirectoryPath string) (*Plugin, error) {
|
||||
p.logger.Info(fmt.Sprintf("Loading plugin from %s", pluginDirectoryPath))
|
||||
|
||||
var plug Plugin
|
||||
|
||||
yamlFile, err := ioutil.ReadFile(filepath.Join(pluginDirectoryPath, "config.yaml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = yaml.Unmarshal(yamlFile, &plug.Details)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.logger.Info(fmt.Sprintf("Loading config for \"%s\" = %+v", plug.Details.Name, plug.Details.Config))
|
||||
|
||||
pluginSo, err := plugin.Open(filepath.Join(pluginDirectoryPath, "plugin.so"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
startFunc, err := pluginSo.Lookup("Start")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
versionFunc, err := pluginSo.Lookup("Version")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
plug.Start = startFunc.(func(*PluginToolkit, map[string]interface{}, context.Context, chan (error)))
|
||||
plug.Version = versionFunc.(func())
|
||||
|
||||
return &plug, nil
|
||||
}
|
||||
0
plugins/.gitkeep
Normal file
0
plugins/.gitkeep
Normal file
17
plugins/build.sh
Executable file
17
plugins/build.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#! /bin/bash
|
||||
|
||||
for d in plugins/*/ ; do
|
||||
CWD=$(pwd)
|
||||
echo "fetch dependencies $d"
|
||||
cd "$d"
|
||||
./dependencies.sh
|
||||
cd "$CWD"
|
||||
done
|
||||
|
||||
for d in plugins/*/ ; do
|
||||
CWD=$(pwd)
|
||||
echo "building $d"
|
||||
cd "$d"
|
||||
/usr/local/go/bin/go build -buildmode=plugin -ldflags "-extldflags '-Wl,-z,stack-size=0x800000'" -tags "urfave_cli_no_docs,ckzg,purego" -trimpath -v -o plugin.so
|
||||
cd "$CWD"
|
||||
done
|
||||
15
pull_plugins.sh
Executable file
15
pull_plugins.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#! /bin/bash
|
||||
|
||||
arrPLUGINS=(${PLUGIN_REPOSITORIES//;/ })
|
||||
|
||||
mkdir -p /tmp/plugins
|
||||
CWD=$(pwd)
|
||||
cd ./plugins
|
||||
|
||||
for i in "${arrPLUGINS[@]}"
|
||||
do
|
||||
echo "cloning $i"
|
||||
git clone "$i"
|
||||
done
|
||||
|
||||
cd $CWD
|
||||
Loading…
Reference in a new issue