From 168b50e8ec1f1a4a5cdfa6a3038fe96af643023a Mon Sep 17 00:00:00 2001 From: rian Date: Wed, 25 Sep 2024 10:33:40 +0300 Subject: [PATCH 01/21] Plugin --- blockchain/blockchain.go | 21 ++++++++++++++++- cmd/juno/juno.go | 4 ++++ core/state.go | 4 ++-- go.mod | 1 + go.sum | 2 ++ grpc/gen/kv_grpc.pb.go | 2 +- node/node.go | 11 +++++++++ plugin/plugin.go | 43 +++++++++++++++++++++++++++++++++ sync/sync.go | 51 +++++++++++++++++++++++++++++++++++++++- 9 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 plugin/plugin.go diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index ee2411c471..7f0d4024ac 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -828,6 +828,26 @@ func (b *Blockchain) RevertHead() error { return b.database.Update(b.revertHead) } +func (b *Blockchain) GetReverseStateDiff() (*core.StateDiff, error) { + var reverseStateDiff *core.StateDiff + return reverseStateDiff, b.database.View(func(txn db.Transaction) error { + blockNumber, err := chainHeight(txn) + if err != nil { + return err + } + stateUpdate, err := stateUpdateByNumber(txn, blockNumber) + if err != nil { + return err + } + state := core.NewState(txn) + reverseStateDiff, err = state.BuildReverseDiff(blockNumber, stateUpdate.StateDiff) + if err != nil { + return err + } + return nil + }) +} + func (b *Blockchain) revertHead(txn db.Transaction) error { blockNumber, err := chainHeight(txn) if err != nil { @@ -874,7 +894,6 @@ func (b *Blockchain) revertHead(txn db.Transaction) error { } // Revert chain height and pending. - if genesisBlock { if err = txn.Delete(db.Pending.Key()); err != nil { return err diff --git a/cmd/juno/juno.go b/cmd/juno/juno.go index c61ee40529..1c8e046196 100644 --- a/cmd/juno/juno.go +++ b/cmd/juno/juno.go @@ -82,6 +82,7 @@ const ( callMaxStepsF = "rpc-call-max-steps" corsEnableF = "rpc-cors-enable" versionedConstantsFileF = "versioned-constants-file" + pluginPathF = "plugin-path" defaultConfig = "" defaulHost = "localhost" @@ -119,6 +120,7 @@ const ( defaultGwTimeout = 5 * time.Second defaultCorsEnable = false defaultVersionedConstantsFile = "" + defaultPluginPath = "" configFlagUsage = "The YAML configuration file." logLevelFlagUsage = "Options: trace, debug, info, warn, error." @@ -170,6 +172,7 @@ const ( "The upper limit is 4 million steps, and any higher value will still be capped at 4 million." corsEnableUsage = "Enable CORS on RPC endpoints" versionedConstantsFileUsage = "Use custom versioned constants from provided file" + pluginPathUsage = "Path to the plugins .so file" ) var Version string @@ -355,6 +358,7 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr junoCmd.Flags().Bool(corsEnableF, defaultCorsEnable, corsEnableUsage) junoCmd.Flags().String(versionedConstantsFileF, defaultVersionedConstantsFile, versionedConstantsFileUsage) junoCmd.MarkFlagsMutuallyExclusive(p2pFeederNodeF, p2pPeersF) + junoCmd.Flags().String(pluginPathF, defaultPluginPath, pluginPathUsage) junoCmd.AddCommand(GenP2PKeyPair(), DBCmd(defaultDBPath)) diff --git a/core/state.go b/core/state.go index 71198afbe6..2184d6efda 100644 --- a/core/state.go +++ b/core/state.go @@ -541,7 +541,7 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { } // update contracts - reversedDiff, err := s.buildReverseDiff(blockNumber, update.StateDiff) + reversedDiff, err := s.BuildReverseDiff(blockNumber, update.StateDiff) if err != nil { return fmt.Errorf("build reverse diff: %v", err) } @@ -657,7 +657,7 @@ func (s *State) purgeContract(addr *felt.Felt) error { return storageCloser() } -func (s *State) buildReverseDiff(blockNumber uint64, diff *StateDiff) (*StateDiff, error) { +func (s *State) BuildReverseDiff(blockNumber uint64, diff *StateDiff) (*StateDiff, error) { reversed := *diff // storage diffs diff --git a/go.mod b/go.mod index a16069fe74..1ae0760226 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/ethereum/go-ethereum v1.14.9 github.com/fxamacker/cbor/v2 v2.7.0 github.com/go-playground/validator/v10 v10.22.1 + github.com/golang/protobuf v1.5.4 github.com/jinzhu/copier v0.4.0 github.com/libp2p/go-libp2p v0.36.2 github.com/libp2p/go-libp2p-kad-dht v0.26.1 diff --git a/go.sum b/go.sum index 1c7399d803..cfb11fe71d 100644 --- a/go.sum +++ b/go.sum @@ -179,6 +179,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= diff --git a/grpc/gen/kv_grpc.pb.go b/grpc/gen/kv_grpc.pb.go index d64c885b5c..b398effbb7 100644 --- a/grpc/gen/kv_grpc.pb.go +++ b/grpc/gen/kv_grpc.pb.go @@ -11,7 +11,7 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" + emptypb "github.com/golang/protobuf/ptypes/empty" ) // This is a compile-time assertion to ensure that this generated file diff --git a/node/node.go b/node/node.go index abd0b93990..74ab5364e8 100644 --- a/node/node.go +++ b/node/node.go @@ -22,6 +22,7 @@ import ( "github.com/NethermindEth/juno/l1" "github.com/NethermindEth/juno/migration" "github.com/NethermindEth/juno/p2p" + junoplugin "github.com/NethermindEth/juno/plugin" "github.com/NethermindEth/juno/rpc" "github.com/NethermindEth/juno/service" adaptfeeder "github.com/NethermindEth/juno/starknetdata/feeder" @@ -88,6 +89,8 @@ type Config struct { GatewayAPIKey string `mapstructure:"gw-api-key"` GatewayTimeout time.Duration `mapstructure:"gw-timeout"` + + PluginPath string `mapstructure:"plugin-path"` } type Node struct { @@ -156,6 +159,14 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen synchronizer := sync.New(chain, adaptfeeder.New(client), log, cfg.PendingPollInterval, dbIsRemote) gatewayClient := gateway.NewClient(cfg.Network.GatewayURL, log).WithUserAgent(ua).WithAPIKey(cfg.GatewayAPIKey) + if cfg.PluginPath != "" { + plugin, err := junoplugin.Load(cfg.PluginPath) + if err != nil { + return nil, err + } + synchronizer.WithPlugin(plugin) + } + var p2pService *p2p.Service if cfg.P2P { if cfg.Network != utils.Sepolia { diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000000..5ccf984e2d --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,43 @@ +package junoplugin + +import ( + "fmt" + "plugin" + + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/felt" +) + +//go:generate mockgen -destination=../mocks/mock_plugin.go -package=mocks github.com/NethermindEth/juno/plugin JunoPlugin +type JunoPlugin interface { + Init() error + Shutdown() error // Todo: Currently this function will never be called. + NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error + // The state is reverted by applying a write operation with the reverseStateDiff's StorageDiffs, Nonces, and ReplacedClasses, + // and a delete option with its DeclaredV0Classes, DeclaredV1Classes, and ReplacedClasses. + RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error +} + +type BlockAndStateUpdate struct { + Block *core.Block + StateUpdate *core.StateUpdate +} + +func Load(pluginPath string) (JunoPlugin, error) { + plug, err := plugin.Open(pluginPath) + if err != nil { + return nil, fmt.Errorf("error loading plugin .so file: %w", err) + } + + symPlugin, err := plug.Lookup("JunoPluginInstance") + if err != nil { + return nil, fmt.Errorf("error looking up PluginInstance: %w", err) + } + + pluginInstance, ok := symPlugin.(JunoPlugin) + if !ok { + return nil, fmt.Errorf("the plugin does not staisfy the required interface") + } + + return pluginInstance, pluginInstance.Init() +} diff --git a/sync/sync.go b/sync/sync.go index a2e4ac0bf0..fb067b9110 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -12,6 +12,7 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/feed" + junoplugin "github.com/NethermindEth/juno/plugin" "github.com/NethermindEth/juno/service" "github.com/NethermindEth/juno/starknetdata" "github.com/NethermindEth/juno/utils" @@ -72,6 +73,7 @@ type Synchronizer struct { pendingPollInterval time.Duration catchUpMode bool + plugin *junoplugin.JunoPlugin } func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, @@ -89,6 +91,12 @@ func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, return s } +// WithPlugin registers an plugin +func (s *Synchronizer) WithPlugin(plugin junoplugin.JunoPlugin) *Synchronizer { + s.plugin = &plugin + return s +} + // WithListener registers an EventListener func (s *Synchronizer) WithListener(listener EventListener) *Synchronizer { s.listener = listener @@ -180,6 +188,32 @@ func (s *Synchronizer) fetchUnknownClasses(ctx context.Context, stateUpdate *cor return newClasses, closer() } +func (s *Synchronizer) handlePluginRevertBlock(block *core.Block, stateUpdate *core.StateUpdate, reverseStateDiff *core.StateDiff) { + if s.plugin == nil { + return + } + + toBlock, err := s.blockchain.Head() + if err != nil { + s.log.Warnw("Failed to retrieve the reverted blockchain head block for the plugin", "err", err) + return + } + + toSU, err := s.blockchain.StateUpdateByNumber(toBlock.Number) + if err != nil { + s.log.Warnw("Failed to retrieve the reverted blockchain head state-update for the plugin", "err", err) + return + } + + err = (*s.plugin).RevertBlock( + &junoplugin.BlockAndStateUpdate{Block: block, StateUpdate: stateUpdate}, + &junoplugin.BlockAndStateUpdate{Block: toBlock, StateUpdate: toSU}, + reverseStateDiff) + if err != nil { + s.log.Errorw("Plugin RevertBlock failure:", "err", err) + } +} + func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class, resetStreams context.CancelFunc, ) stream.Callback { @@ -205,7 +239,14 @@ func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stat // revert the head and restart the sync process, hoping that the reorg is not deep // if the reorg is deeper, we will end up here again and again until we fully revert reorged // blocks + reverseStateDiff, err := s.blockchain.GetReverseStateDiff() + if err != nil { + s.log.Warnw("Failed to retrieve reverse state diff", "head", block.Number, "hash", block.Hash.ShortString(), "err", err) + } s.revertHead(block) + if s.plugin != nil { + s.handlePluginRevertBlock(block, stateUpdate, reverseStateDiff) + } } else { s.log.Warnw("Failed storing Block", "number", block.Number, "hash", block.Hash.ShortString(), "err", err) @@ -231,6 +272,12 @@ func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stat s.newHeads.Send(block.Header) s.log.Infow("Stored Block", "number", block.Number, "hash", block.Hash.ShortString(), "root", block.GlobalStateRoot.ShortString()) + if s.plugin != nil { + err := (*s.plugin).NewBlock(block, stateUpdate, newClasses) + if err != nil { + s.log.Errorw("Plugin NewBlock failure:", err) + } + } } } } @@ -317,7 +364,9 @@ func (s *Synchronizer) revertHead(forkBlock *core.Block) { } s.log.Infow("Reorg detected", "localHead", localHead, "forkHead", forkBlock.Hash) - + if err != nil { + s.log.Warnw("Failed getting reverse state-diff, err: ", err) + } err = s.blockchain.RevertHead() if err != nil { s.log.Warnw("Failed reverting HEAD", "reverted", localHead, "err", err) From 1439c6094dafbb4d62bff29c60f8c3fd8e62918f Mon Sep 17 00:00:00 2001 From: rian Date: Wed, 14 Aug 2024 11:50:05 +0300 Subject: [PATCH 02/21] revert dep change --- grpc/gen/kv_grpc.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc/gen/kv_grpc.pb.go b/grpc/gen/kv_grpc.pb.go index b398effbb7..d64c885b5c 100644 --- a/grpc/gen/kv_grpc.pb.go +++ b/grpc/gen/kv_grpc.pb.go @@ -11,7 +11,7 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - emptypb "github.com/golang/protobuf/ptypes/empty" + emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file From 5df86485cbf08aa6dfd0cc7fa967695a80199799 Mon Sep 17 00:00:00 2001 From: rian Date: Fri, 16 Aug 2024 15:53:49 +0300 Subject: [PATCH 03/21] pass rpcHandler to plugin (+refactor for import cycle) --- node/node.go | 21 +++++++++++++-------- plugin/plugin.go | 20 ++++++-------------- plugin/sync/pluginsync.go | 18 ++++++++++++++++++ sync/sync.go | 2 +- 4 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 plugin/sync/pluginsync.go diff --git a/node/node.go b/node/node.go index 74ab5364e8..bd71995d3b 100644 --- a/node/node.go +++ b/node/node.go @@ -159,14 +159,6 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen synchronizer := sync.New(chain, adaptfeeder.New(client), log, cfg.PendingPollInterval, dbIsRemote) gatewayClient := gateway.NewClient(cfg.Network.GatewayURL, log).WithUserAgent(ua).WithAPIKey(cfg.GatewayAPIKey) - if cfg.PluginPath != "" { - plugin, err := junoplugin.Load(cfg.PluginPath) - if err != nil { - return nil, err - } - synchronizer.WithPlugin(plugin) - } - var p2pService *p2p.Service if cfg.P2P { if cfg.Network != utils.Sepolia { @@ -200,6 +192,19 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen rpcHandler := rpc.New(chain, syncReader, throttledVM, version, log).WithGateway(gatewayClient).WithFeeder(client) rpcHandler = rpcHandler.WithFilterLimit(cfg.RPCMaxBlockScan).WithCallMaxSteps(uint64(cfg.RPCCallMaxSteps)) services = append(services, rpcHandler) + + if cfg.PluginPath != "" { + plugin, err := junoplugin.Load(cfg.PluginPath) + if err != nil { + return nil, err + } + err = plugin.Init(rpcHandler) + if err != nil { + return nil, err + } + synchronizer.WithPlugin(plugin) + } + // to improve RPC throughput we double GOMAXPROCS maxGoroutines := 2 * runtime.GOMAXPROCS(0) jsonrpcServer := jsonrpc.NewServer(maxGoroutines, log).WithValidator(validator.Validator()) diff --git a/plugin/plugin.go b/plugin/plugin.go index 5ccf984e2d..9cc5572462 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -4,23 +4,15 @@ import ( "fmt" "plugin" - "github.com/NethermindEth/juno/core" - "github.com/NethermindEth/juno/core/felt" + junopluginsync "github.com/NethermindEth/juno/plugin/sync" + "github.com/NethermindEth/juno/rpc" ) //go:generate mockgen -destination=../mocks/mock_plugin.go -package=mocks github.com/NethermindEth/juno/plugin JunoPlugin type JunoPlugin interface { - Init() error - Shutdown() error // Todo: Currently this function will never be called. - NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error - // The state is reverted by applying a write operation with the reverseStateDiff's StorageDiffs, Nonces, and ReplacedClasses, - // and a delete option with its DeclaredV0Classes, DeclaredV1Classes, and ReplacedClasses. - RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error -} - -type BlockAndStateUpdate struct { - Block *core.Block - StateUpdate *core.StateUpdate + Init(rpcHandler *rpc.Handler) error + ShutDown() // Todo: Currently this function will never be called. + junopluginsync.JunoPlugin } func Load(pluginPath string) (JunoPlugin, error) { @@ -39,5 +31,5 @@ func Load(pluginPath string) (JunoPlugin, error) { return nil, fmt.Errorf("the plugin does not staisfy the required interface") } - return pluginInstance, pluginInstance.Init() + return pluginInstance, nil } diff --git a/plugin/sync/pluginsync.go b/plugin/sync/pluginsync.go new file mode 100644 index 0000000000..c57cfb3215 --- /dev/null +++ b/plugin/sync/pluginsync.go @@ -0,0 +1,18 @@ +package junopluginsync + +import ( + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/felt" +) + +type BlockAndStateUpdate struct { + Block *core.Block + StateUpdate *core.StateUpdate +} + +type JunoPlugin interface { + NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error + // The state is reverted by applying a write operation with the reverseStateDiff's StorageDiffs, Nonces, and ReplacedClasses, + // and a delete option with its DeclaredV0Classes, DeclaredV1Classes, and ReplacedClasses. + RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error +} diff --git a/sync/sync.go b/sync/sync.go index fb067b9110..6e243cf4c4 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -12,7 +12,7 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/feed" - junoplugin "github.com/NethermindEth/juno/plugin" + junoplugin "github.com/NethermindEth/juno/plugin/sync" "github.com/NethermindEth/juno/service" "github.com/NethermindEth/juno/starknetdata" "github.com/NethermindEth/juno/utils" From 1559b80898a7d556e8064f89b550412875518f8d Mon Sep 17 00:00:00 2001 From: rian Date: Mon, 19 Aug 2024 10:39:27 +0300 Subject: [PATCH 04/21] Revert "pass rpcHandler to plugin (+refactor for import cycle)" This reverts commit 109fa90470432ff4754651d4d81f1348c6370ffd. --- node/node.go | 21 ++++++++------------- plugin/plugin.go | 20 ++++++++++++++------ plugin/sync/pluginsync.go | 18 ------------------ sync/sync.go | 2 +- 4 files changed, 23 insertions(+), 38 deletions(-) delete mode 100644 plugin/sync/pluginsync.go diff --git a/node/node.go b/node/node.go index bd71995d3b..74ab5364e8 100644 --- a/node/node.go +++ b/node/node.go @@ -159,6 +159,14 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen synchronizer := sync.New(chain, adaptfeeder.New(client), log, cfg.PendingPollInterval, dbIsRemote) gatewayClient := gateway.NewClient(cfg.Network.GatewayURL, log).WithUserAgent(ua).WithAPIKey(cfg.GatewayAPIKey) + if cfg.PluginPath != "" { + plugin, err := junoplugin.Load(cfg.PluginPath) + if err != nil { + return nil, err + } + synchronizer.WithPlugin(plugin) + } + var p2pService *p2p.Service if cfg.P2P { if cfg.Network != utils.Sepolia { @@ -192,19 +200,6 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen rpcHandler := rpc.New(chain, syncReader, throttledVM, version, log).WithGateway(gatewayClient).WithFeeder(client) rpcHandler = rpcHandler.WithFilterLimit(cfg.RPCMaxBlockScan).WithCallMaxSteps(uint64(cfg.RPCCallMaxSteps)) services = append(services, rpcHandler) - - if cfg.PluginPath != "" { - plugin, err := junoplugin.Load(cfg.PluginPath) - if err != nil { - return nil, err - } - err = plugin.Init(rpcHandler) - if err != nil { - return nil, err - } - synchronizer.WithPlugin(plugin) - } - // to improve RPC throughput we double GOMAXPROCS maxGoroutines := 2 * runtime.GOMAXPROCS(0) jsonrpcServer := jsonrpc.NewServer(maxGoroutines, log).WithValidator(validator.Validator()) diff --git a/plugin/plugin.go b/plugin/plugin.go index 9cc5572462..5ccf984e2d 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -4,15 +4,23 @@ import ( "fmt" "plugin" - junopluginsync "github.com/NethermindEth/juno/plugin/sync" - "github.com/NethermindEth/juno/rpc" + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/felt" ) //go:generate mockgen -destination=../mocks/mock_plugin.go -package=mocks github.com/NethermindEth/juno/plugin JunoPlugin type JunoPlugin interface { - Init(rpcHandler *rpc.Handler) error - ShutDown() // Todo: Currently this function will never be called. - junopluginsync.JunoPlugin + Init() error + Shutdown() error // Todo: Currently this function will never be called. + NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error + // The state is reverted by applying a write operation with the reverseStateDiff's StorageDiffs, Nonces, and ReplacedClasses, + // and a delete option with its DeclaredV0Classes, DeclaredV1Classes, and ReplacedClasses. + RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error +} + +type BlockAndStateUpdate struct { + Block *core.Block + StateUpdate *core.StateUpdate } func Load(pluginPath string) (JunoPlugin, error) { @@ -31,5 +39,5 @@ func Load(pluginPath string) (JunoPlugin, error) { return nil, fmt.Errorf("the plugin does not staisfy the required interface") } - return pluginInstance, nil + return pluginInstance, pluginInstance.Init() } diff --git a/plugin/sync/pluginsync.go b/plugin/sync/pluginsync.go deleted file mode 100644 index c57cfb3215..0000000000 --- a/plugin/sync/pluginsync.go +++ /dev/null @@ -1,18 +0,0 @@ -package junopluginsync - -import ( - "github.com/NethermindEth/juno/core" - "github.com/NethermindEth/juno/core/felt" -) - -type BlockAndStateUpdate struct { - Block *core.Block - StateUpdate *core.StateUpdate -} - -type JunoPlugin interface { - NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error - // The state is reverted by applying a write operation with the reverseStateDiff's StorageDiffs, Nonces, and ReplacedClasses, - // and a delete option with its DeclaredV0Classes, DeclaredV1Classes, and ReplacedClasses. - RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error -} diff --git a/sync/sync.go b/sync/sync.go index 6e243cf4c4..fb067b9110 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -12,7 +12,7 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/feed" - junoplugin "github.com/NethermindEth/juno/plugin/sync" + junoplugin "github.com/NethermindEth/juno/plugin" "github.com/NethermindEth/juno/service" "github.com/NethermindEth/juno/starknetdata" "github.com/NethermindEth/juno/utils" From 048c9e210a5d4a32f2f08aca9e9134d64a7dc1ff Mon Sep 17 00:00:00 2001 From: rian Date: Thu, 5 Sep 2024 14:39:51 +0300 Subject: [PATCH 05/21] address comment --- sync/sync.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sync/sync.go b/sync/sync.go index fb067b9110..110e364cb3 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -73,7 +73,7 @@ type Synchronizer struct { pendingPollInterval time.Duration catchUpMode bool - plugin *junoplugin.JunoPlugin + plugin junoplugin.JunoPlugin } func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, @@ -93,7 +93,7 @@ func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, // WithPlugin registers an plugin func (s *Synchronizer) WithPlugin(plugin junoplugin.JunoPlugin) *Synchronizer { - s.plugin = &plugin + s.plugin = plugin return s } @@ -205,7 +205,7 @@ func (s *Synchronizer) handlePluginRevertBlock(block *core.Block, stateUpdate *c return } - err = (*s.plugin).RevertBlock( + err = (s.plugin).RevertBlock( &junoplugin.BlockAndStateUpdate{Block: block, StateUpdate: stateUpdate}, &junoplugin.BlockAndStateUpdate{Block: toBlock, StateUpdate: toSU}, reverseStateDiff) @@ -273,7 +273,7 @@ func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stat s.log.Infow("Stored Block", "number", block.Number, "hash", block.Hash.ShortString(), "root", block.GlobalStateRoot.ShortString()) if s.plugin != nil { - err := (*s.plugin).NewBlock(block, stateUpdate, newClasses) + err := (s.plugin).NewBlock(block, stateUpdate, newClasses) if err != nil { s.log.Errorw("Plugin NewBlock failure:", err) } From e403b72ef4ff325f2a2c6fafa7050150e1710059 Mon Sep 17 00:00:00 2001 From: rian Date: Tue, 24 Sep 2024 16:02:18 +0300 Subject: [PATCH 06/21] Implement test --- mocks/mock_plugin.go | 98 ++++++++++++++++++++++++++++++++ plugin/plugin_test.go | 80 ++++++++++++++++++++++++++ sync/sync.go | 37 +++++++----- vm/rust/src/juno_state_reader.rs | 15 +++-- 4 files changed, 213 insertions(+), 17 deletions(-) create mode 100644 mocks/mock_plugin.go create mode 100644 plugin/plugin_test.go diff --git a/mocks/mock_plugin.go b/mocks/mock_plugin.go new file mode 100644 index 0000000000..7c1d4a4391 --- /dev/null +++ b/mocks/mock_plugin.go @@ -0,0 +1,98 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/NethermindEth/juno/plugin (interfaces: JunoPlugin) +// +// Generated by this command: +// +// mockgen -destination=../mocks/mock_plugin.go -package=mocks github.com/NethermindEth/juno/plugin JunoPlugin +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + core "github.com/NethermindEth/juno/core" + felt "github.com/NethermindEth/juno/core/felt" + junoplugin "github.com/NethermindEth/juno/plugin" + gomock "go.uber.org/mock/gomock" +) + +// MockJunoPlugin is a mock of JunoPlugin interface. +type MockJunoPlugin struct { + ctrl *gomock.Controller + recorder *MockJunoPluginMockRecorder +} + +// MockJunoPluginMockRecorder is the mock recorder for MockJunoPlugin. +type MockJunoPluginMockRecorder struct { + mock *MockJunoPlugin +} + +// NewMockJunoPlugin creates a new mock instance. +func NewMockJunoPlugin(ctrl *gomock.Controller) *MockJunoPlugin { + mock := &MockJunoPlugin{ctrl: ctrl} + mock.recorder = &MockJunoPluginMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockJunoPlugin) EXPECT() *MockJunoPluginMockRecorder { + return m.recorder +} + +// Init mocks base method. +func (m *MockJunoPlugin) Init() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Init") + ret0, _ := ret[0].(error) + return ret0 +} + +// Init indicates an expected call of Init. +func (mr *MockJunoPluginMockRecorder) Init() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockJunoPlugin)(nil).Init)) +} + +// NewBlock mocks base method. +func (m *MockJunoPlugin) NewBlock(arg0 *core.Block, arg1 *core.StateUpdate, arg2 map[felt.Felt]core.Class) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewBlock", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// NewBlock indicates an expected call of NewBlock. +func (mr *MockJunoPluginMockRecorder) NewBlock(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBlock", reflect.TypeOf((*MockJunoPlugin)(nil).NewBlock), arg0, arg1, arg2) +} + +// RevertBlock mocks base method. +func (m *MockJunoPlugin) RevertBlock(arg0, arg1 *junoplugin.BlockAndStateUpdate, arg2 *core.StateDiff) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevertBlock", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevertBlock indicates an expected call of RevertBlock. +func (mr *MockJunoPluginMockRecorder) RevertBlock(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevertBlock", reflect.TypeOf((*MockJunoPlugin)(nil).RevertBlock), arg0, arg1, arg2) +} + +// Shutdown mocks base method. +func (m *MockJunoPlugin) Shutdown() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Shutdown") + ret0, _ := ret[0].(error) + return ret0 +} + +// Shutdown indicates an expected call of Shutdown. +func (mr *MockJunoPluginMockRecorder) Shutdown() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockJunoPlugin)(nil).Shutdown)) +} diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go new file mode 100644 index 0000000000..16d679e71d --- /dev/null +++ b/plugin/plugin_test.go @@ -0,0 +1,80 @@ +package junoplugin_test + +import ( + "context" + "testing" + "time" + + "github.com/NethermindEth/juno/blockchain" + "github.com/NethermindEth/juno/clients/feeder" + "github.com/NethermindEth/juno/db/pebble" + "github.com/NethermindEth/juno/mocks" + junoplugin "github.com/NethermindEth/juno/plugin" + adaptfeeder "github.com/NethermindEth/juno/starknetdata/feeder" + "github.com/NethermindEth/juno/sync" + "github.com/NethermindEth/juno/utils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +const timeout = time.Second + +func TestPlugin(t *testing.T) { + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + + plugin := mocks.NewMockJunoPlugin(mockCtrl) + + mainClient := feeder.NewTestClient(t, &utils.Mainnet) + mainGw := adaptfeeder.New(mainClient) + + integClient := feeder.NewTestClient(t, &utils.Integration) + integGw := adaptfeeder.New(integClient) + + testDB := pebble.NewMemTest(t) + + // sync to integration for 2 blocks + for i := range 2 { + su, block, err := integGw.StateUpdateWithBlock(context.Background(), uint64(i)) + require.NoError(t, err) + plugin.EXPECT().NewBlock(block, su, gomock.Any()) + } + bc := blockchain.New(testDB, &utils.Integration) + synchronizer := sync.New(bc, integGw, utils.NewNopZapLogger(), 0, false).WithPlugin(plugin) + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + require.NoError(t, synchronizer.Run(ctx)) + cancel() + + t.Run("resync to mainnet with the same db", func(t *testing.T) { + bc := blockchain.New(testDB, &utils.Mainnet) + + // Ensure current head is Integration head + head, err := bc.HeadsHeader() + require.NoError(t, err) + require.Equal(t, utils.HexToFelt(t, "0x34e815552e42c5eb5233b99de2d3d7fd396e575df2719bf98e7ed2794494f86"), head.Hash) + + // Reorg 2 blocks, then sync 3 blocks + su1, block1, err := integGw.StateUpdateWithBlock(context.Background(), uint64(1)) + require.NoError(t, err) + su0, block0, err := integGw.StateUpdateWithBlock(context.Background(), uint64(0)) + require.NoError(t, err) + plugin.EXPECT().RevertBlock(&junoplugin.BlockAndStateUpdate{block1, su1}, &junoplugin.BlockAndStateUpdate{block0, su0}, gomock.Any()) + plugin.EXPECT().RevertBlock(&junoplugin.BlockAndStateUpdate{block0, su0}, &junoplugin.BlockAndStateUpdate{nil, nil}, gomock.Any()) + for i := range 3 { + su, block, err := mainGw.StateUpdateWithBlock(context.Background(), uint64(i)) + require.NoError(t, err) + plugin.EXPECT().NewBlock(block, su, gomock.Any()) + } + + synchronizer = sync.New(bc, mainGw, utils.NewNopZapLogger(), 0, false).WithPlugin(plugin) + ctx, cancel = context.WithTimeout(context.Background(), timeout) + require.NoError(t, synchronizer.Run(ctx)) + cancel() + + // After syncing (and reorging) the current head should be at mainnet + head, err = bc.HeadsHeader() + require.NoError(t, err) + require.Equal(t, utils.HexToFelt(t, "0x4e1f77f39545afe866ac151ac908bd1a347a2a8a7d58bef1276db4f06fdf2f6"), head.Hash) + }) +} diff --git a/sync/sync.go b/sync/sync.go index 110e364cb3..e178c7310e 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -188,25 +188,40 @@ func (s *Synchronizer) fetchUnknownClasses(ctx context.Context, stateUpdate *cor return newClasses, closer() } -func (s *Synchronizer) handlePluginRevertBlock(block *core.Block, stateUpdate *core.StateUpdate, reverseStateDiff *core.StateDiff) { +func (s *Synchronizer) handlePluginRevertBlock() { if s.plugin == nil { return } - - toBlock, err := s.blockchain.Head() + fromBlock, err := s.blockchain.Head() if err != nil { s.log.Warnw("Failed to retrieve the reverted blockchain head block for the plugin", "err", err) return } - - toSU, err := s.blockchain.StateUpdateByNumber(toBlock.Number) + fromSU, err := s.blockchain.StateUpdateByNumber(fromBlock.Number) if err != nil { s.log.Warnw("Failed to retrieve the reverted blockchain head state-update for the plugin", "err", err) return } - + reverseStateDiff, err := s.blockchain.GetReverseStateDiff() + if err != nil { + s.log.Warnw("Failed to retrieve reverse state diff", "head", fromBlock.Number, "hash", fromBlock.Hash.ShortString(), "err", err) + } + var toBlock *core.Block + var toSU *core.StateUpdate + if fromBlock.Number != 0 { + toBlock, err = s.blockchain.BlockByHash(fromBlock.ParentHash) + if err != nil { + s.log.Warnw("Failed to retrieve the parent block for the plugin", "err", err) + return + } + toSU, err = s.blockchain.StateUpdateByNumber(toBlock.Number) + if err != nil { + s.log.Warnw("Failed to retrieve the parents state-update for the plugin", "err", err) + return + } + } err = (s.plugin).RevertBlock( - &junoplugin.BlockAndStateUpdate{Block: block, StateUpdate: stateUpdate}, + &junoplugin.BlockAndStateUpdate{Block: fromBlock, StateUpdate: fromSU}, &junoplugin.BlockAndStateUpdate{Block: toBlock, StateUpdate: toSU}, reverseStateDiff) if err != nil { @@ -239,14 +254,10 @@ func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stat // revert the head and restart the sync process, hoping that the reorg is not deep // if the reorg is deeper, we will end up here again and again until we fully revert reorged // blocks - reverseStateDiff, err := s.blockchain.GetReverseStateDiff() - if err != nil { - s.log.Warnw("Failed to retrieve reverse state diff", "head", block.Number, "hash", block.Hash.ShortString(), "err", err) - } - s.revertHead(block) if s.plugin != nil { - s.handlePluginRevertBlock(block, stateUpdate, reverseStateDiff) + s.handlePluginRevertBlock() } + s.revertHead(block) } else { s.log.Warnw("Failed storing Block", "number", block.Number, "hash", block.Hash.ShortString(), "err", err) diff --git a/vm/rust/src/juno_state_reader.rs b/vm/rust/src/juno_state_reader.rs index 71f4e575fa..32a8e5d063 100644 --- a/vm/rust/src/juno_state_reader.rs +++ b/vm/rust/src/juno_state_reader.rs @@ -1,5 +1,5 @@ use std::{ - ffi::{c_char, c_uchar, c_void, c_int, CStr}, + ffi::{c_char, c_int, c_uchar, c_void, CStr}, slice, sync::Mutex, }; @@ -75,8 +75,14 @@ impl StateReader for JunoStateReader { let addr = felt_to_byte_array(contract_address.0.key()); let storage_key = felt_to_byte_array(key.0.key()); let mut buffer: [u8; 32] = [0; 32]; - let wrote = - unsafe { JunoStateGetStorageAt(self.handle, addr.as_ptr(), storage_key.as_ptr(), buffer.as_mut_ptr()) }; + let wrote = unsafe { + JunoStateGetStorageAt( + self.handle, + addr.as_ptr(), + storage_key.as_ptr(), + buffer.as_mut_ptr(), + ) + }; if wrote == 0 { Err(StateError::StateReadError(format!( "failed to read location {} at address {}", @@ -111,7 +117,8 @@ impl StateReader for JunoStateReader { fn get_class_hash_at(&self, contract_address: ContractAddress) -> StateResult { let addr = felt_to_byte_array(contract_address.0.key()); let mut buffer: [u8; 32] = [0; 32]; - let wrote = unsafe { JunoStateGetClassHashAt(self.handle, addr.as_ptr(), buffer.as_mut_ptr()) }; + let wrote = + unsafe { JunoStateGetClassHashAt(self.handle, addr.as_ptr(), buffer.as_mut_ptr()) }; if wrote == 0 { Err(StateError::StateReadError(format!( "failed to read class hash of address {}", From b09ab5f345178bb4e4fc3f3cbb45ad96bb8355a1 Mon Sep 17 00:00:00 2001 From: rian Date: Thu, 26 Sep 2024 11:22:15 +0300 Subject: [PATCH 07/21] add docs --- blockchain/blockchain.go | 5 +-- docs/docs/plugin.md | 87 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 docs/docs/plugin.md diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 7f0d4024ac..67cdc08c56 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -841,10 +841,7 @@ func (b *Blockchain) GetReverseStateDiff() (*core.StateDiff, error) { } state := core.NewState(txn) reverseStateDiff, err = state.BuildReverseDiff(blockNumber, stateUpdate.StateDiff) - if err != nil { - return err - } - return nil + return err }) } diff --git a/docs/docs/plugin.md b/docs/docs/plugin.md new file mode 100644 index 0000000000..197cc0196b --- /dev/null +++ b/docs/docs/plugin.md @@ -0,0 +1,87 @@ +--- +title: Juno Plugins +--- + +## Overview + +The Juno client now supports plugins that satisfy the `JunoPlugin` interface. This enhancement allows developers to extend and customize Juno's behavior by dynamically loading external plugins at runtime. + +The `JunoPlugin` interface provides a structured way for plugins to interact with the blockchain by receiving notifications when new blocks are added or reverted. This ensures state consistency, especially during blockchain reorganizations, while abstracting away the complexity of implementing block syncing and revert logic. + +## The JunoPlugin Interface + +Your plugin must implement the `JunoPlugin` interface, which includes methods for initializing, shutting down, and handling new and reverted blocks. + + +```go +type JunoPlugin interface { + Init() error + Shutdown() error // Note: Currently this function will never be called. + NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error + RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error +} +``` + + +**Init**: Called when the plugin is initialized. This can be used to set up database connections or any other necessary resources. + +**Shutdown**: Called when the plugin is shut down, though this function is currently never called by Juno. This can be used to clean up resources like database connections. + +**NewBlock**: Triggered when a new block is synced by the Juno client. Juno will send the block, the corresponding state update, and any new classes. Importantly, Juno waits for the plugin to finish processing this function call before continuing. This ensures that the plugin completes its task before Juno proceeds with the blockchain sync. + +**RevertBlock**: Called during a blockchain reorganization (reorg). Juno will invoke this method for each block that needs to be reverted. Similar to NewBlock, the client will wait for the plugin to finish handling the revert before moving on to the next block. + +## Example plugin + +Here is a basic example of a plugin that satisfies the `JunoPlugin` interface: + +```go +//go:generate go build -buildmode=plugin -o ../../build/plugin.so ./example.go +type examplePlugin string + +// Important: "JunoPluginInstance" needs to be exported for Juno to load the plugin correctly +var JunoPluginInstance examplePlugin + +var _ junoplugin.JunoPlugin = (*examplePlugin)(nil) + +func (p *examplePlugin) Init() error { + fmt.Println("ExamplePlugin initialized") + return nil +} + +func (p *examplePlugin) Shutdown() error { + fmt.Println("ExamplePlugin shutdown") + return nil +} + +func (p *examplePlugin) NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error { + fmt.Println("ExamplePlugin NewBlock called") + return nil +} + +func (p *examplePlugin) RevertBlock(from, to *junoplugin.BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error { + fmt.Println("ExamplePlugin RevertBlock called") + return nil +} +``` + +The ```JunoPluginInstance``` variable must be exported for Juno to correctly load the plugin: +```var JunoPluginInstance examplePlugin``` + +We ensure the plugin implements the ```JunoPlugin``` interface, with the following line: +```var _ junoplugin.JunoPlugin = (*examplePlugin)(nil)``` + + + +# Building and Loading the Plugin + +Once you have written your plugin, you can compile it into a shared object file (.so) using the following command: +```go build -buildmode=plugin -o ./plugin.so /path/to/your/plugin.go``` + +This command compiles the plugin into a shared object file (```plugin.so```), which can then be loaded by the Juno client using the ```--plugin-path``` flag. + +# Running Juno with the plugin + +Once your plugin has been compiled into a `.so` file, you can run Juno with your plugin by providing the `--plugin-path` flag. This flag tells Juno where to find and load your plugin at runtime. + + From 3dabbb22bc0349e94cc2eb85ffdfa5ce6c425ba2 Mon Sep 17 00:00:00 2001 From: rian Date: Thu, 26 Sep 2024 11:27:05 +0300 Subject: [PATCH 08/21] mention plugins in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index caf3fb5baf..a771c2128a 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,7 @@ After following these steps, Juno should be up and running on your machine, util - Starknet state construction and storage using a path-based Merkle Patricia trie. - Feeder gateway synchronisation of Blocks, Transactions, Receipts, State Updates and Classes. - Block and Transaction hash verification. +- Plugins ## 🛣 Roadmap From e120fe8906495d270394012fdb09a9093350b00a Mon Sep 17 00:00:00 2001 From: rian Date: Mon, 30 Sep 2024 11:51:51 +0300 Subject: [PATCH 09/21] handle pluging shutdon --- node/node.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/node/node.go b/node/node.go index 74ab5364e8..911ced58c2 100644 --- a/node/node.go +++ b/node/node.go @@ -103,6 +103,8 @@ type Node struct { log utils.Logger version string + + plugin junoplugin.JunoPlugin } // New sets the config and logger to the StarknetNode. @@ -159,8 +161,9 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen synchronizer := sync.New(chain, adaptfeeder.New(client), log, cfg.PendingPollInterval, dbIsRemote) gatewayClient := gateway.NewClient(cfg.Network.GatewayURL, log).WithUserAgent(ua).WithAPIKey(cfg.GatewayAPIKey) + var plugin junoplugin.JunoPlugin if cfg.PluginPath != "" { - plugin, err := junoplugin.Load(cfg.PluginPath) + plugin, err = junoplugin.Load(cfg.PluginPath) if err != nil { return nil, err } @@ -268,6 +271,7 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen blockchain: chain, services: services, metricsService: metricsService, + plugin: plugin, } if n.cfg.EthNode == "" { @@ -362,6 +366,13 @@ func (n *Node) Run(ctx context.Context) { return } + if n.plugin != nil { + go func() { + <-ctx.Done() + n.plugin.Shutdown() + }() + } + for _, s := range n.services { wg.Go(func() { // Immediately acknowledge panicing services by shutting down the node From c6b4b3b6aecfcbdb9c9f199eee0c7415a3a5b959 Mon Sep 17 00:00:00 2001 From: rian Date: Mon, 30 Sep 2024 12:01:59 +0300 Subject: [PATCH 10/21] fix no-return bug, and move test const --- plugin/plugin_test.go | 3 +-- sync/sync.go | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 16d679e71d..11389c2c1a 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -17,9 +17,8 @@ import ( "go.uber.org/mock/gomock" ) -const timeout = time.Second - func TestPlugin(t *testing.T) { + timeout := time.Second mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) diff --git a/sync/sync.go b/sync/sync.go index e178c7310e..af0f80fbd5 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -205,6 +205,7 @@ func (s *Synchronizer) handlePluginRevertBlock() { reverseStateDiff, err := s.blockchain.GetReverseStateDiff() if err != nil { s.log.Warnw("Failed to retrieve reverse state diff", "head", fromBlock.Number, "hash", fromBlock.Hash.ShortString(), "err", err) + return } var toBlock *core.Block var toSU *core.StateUpdate @@ -254,9 +255,7 @@ func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stat // revert the head and restart the sync process, hoping that the reorg is not deep // if the reorg is deeper, we will end up here again and again until we fully revert reorged // blocks - if s.plugin != nil { - s.handlePluginRevertBlock() - } + s.handlePluginRevertBlock() s.revertHead(block) } else { s.log.Warnw("Failed storing Block", "number", block.Number, From 7fbfd8c82bbc0b659d64d3c75c85f08244f317eb Mon Sep 17 00:00:00 2001 From: rian Date: Mon, 30 Sep 2024 13:00:42 +0300 Subject: [PATCH 11/21] refactor GetReverseSTateDiff --- blockchain/blockchain.go | 2 +- core/state.go | 50 +++++++++++++++++++++++++++------------- sync/sync.go | 2 ++ 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 67cdc08c56..4aa6659b98 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -840,7 +840,7 @@ func (b *Blockchain) GetReverseStateDiff() (*core.StateDiff, error) { return err } state := core.NewState(txn) - reverseStateDiff, err = state.BuildReverseDiff(blockNumber, stateUpdate.StateDiff) + reverseStateDiff, err = state.GetReverseStateDiff(blockNumber, stateUpdate.StateDiff) return err }) } diff --git a/core/state.go b/core/state.go index 2184d6efda..5f50ad567d 100644 --- a/core/state.go +++ b/core/state.go @@ -540,10 +540,14 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { return fmt.Errorf("remove declared classes: %v", err) } - // update contracts - reversedDiff, err := s.BuildReverseDiff(blockNumber, update.StateDiff) + reversedDiff, err := s.GetReverseStateDiff(blockNumber, update.StateDiff) if err != nil { - return fmt.Errorf("build reverse diff: %v", err) + return fmt.Errorf("error getting reverse state diff: %v", err) + } + + err = s.PerformStateDeletions(blockNumber, update.StateDiff) + if err != nil { + return fmt.Errorf("error performing state deletions: %v", err) } stateTrie, storageCloser, err := s.storage() @@ -657,7 +661,7 @@ func (s *State) purgeContract(addr *felt.Felt) error { return storageCloser() } -func (s *State) BuildReverseDiff(blockNumber uint64, diff *StateDiff) (*StateDiff, error) { +func (s *State) GetReverseStateDiff(blockNumber uint64, diff *StateDiff) (*StateDiff, error) { reversed := *diff // storage diffs @@ -673,10 +677,6 @@ func (s *State) BuildReverseDiff(blockNumber uint64, diff *StateDiff) (*StateDif } value = oldValue } - - if err := s.DeleteContractStorageLog(&addr, &key, blockNumber); err != nil { - return nil, err - } reversedDiffs[key] = value } reversed.StorageDiffs[addr] = reversedDiffs @@ -686,7 +686,6 @@ func (s *State) BuildReverseDiff(blockNumber uint64, diff *StateDiff) (*StateDif reversed.Nonces = make(map[felt.Felt]*felt.Felt, len(diff.Nonces)) for addr := range diff.Nonces { oldNonce := &felt.Zero - if blockNumber > 0 { var err error oldNonce, err = s.ContractNonceAt(&addr, blockNumber-1) @@ -694,10 +693,6 @@ func (s *State) BuildReverseDiff(blockNumber uint64, diff *StateDiff) (*StateDif return nil, err } } - - if err := s.DeleteContractNonceLog(&addr, blockNumber); err != nil { - return nil, err - } reversed.Nonces[addr] = oldNonce } @@ -712,12 +707,35 @@ func (s *State) BuildReverseDiff(blockNumber uint64, diff *StateDiff) (*StateDif return nil, err } } + reversed.ReplacedClasses[addr] = classHash + } + return &reversed, nil +} + +func (s *State) PerformStateDeletions(blockNumber uint64, diff *StateDiff) error { + // storage diffs + for addr, storageDiffs := range diff.StorageDiffs { + for key := range storageDiffs { + if err := s.DeleteContractStorageLog(&addr, &key, blockNumber); err != nil { + return err + } + } + } + + // nonces + for addr := range diff.Nonces { + if err := s.DeleteContractNonceLog(&addr, blockNumber); err != nil { + return err + } + } + + // replaced classes + for addr := range diff.ReplacedClasses { if err := s.DeleteContractClassHashLog(&addr, blockNumber); err != nil { - return nil, err + return err } - reversed.ReplacedClasses[addr] = classHash } - return &reversed, nil + return nil } diff --git a/sync/sync.go b/sync/sync.go index af0f80fbd5..8899c57a04 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -3,6 +3,7 @@ package sync import ( "context" "errors" + "fmt" "runtime" "sync/atomic" "time" @@ -205,6 +206,7 @@ func (s *Synchronizer) handlePluginRevertBlock() { reverseStateDiff, err := s.blockchain.GetReverseStateDiff() if err != nil { s.log.Warnw("Failed to retrieve reverse state diff", "head", fromBlock.Number, "hash", fromBlock.Hash.ShortString(), "err", err) + fmt.Println("werwerwer", err) return } var toBlock *core.Block From 092eb60cc860c462a7b1ec695aabe1b68bb1eb97 Mon Sep 17 00:00:00 2001 From: rian Date: Mon, 30 Sep 2024 16:36:47 +0300 Subject: [PATCH 12/21] lint --- core/state.go | 12 +++++++++--- node/node.go | 5 ++++- sync/sync.go | 2 -- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/state.go b/core/state.go index 5f50ad567d..9e55540774 100644 --- a/core/state.go +++ b/core/state.go @@ -563,6 +563,10 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { return err } + if err = s.purgeNoClassContracts(); err != nil { + return err + } + // purge deployed contracts for addr := range update.StateDiff.DeployedContracts { if err = s.purgeContract(&addr); err != nil { @@ -570,12 +574,15 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { } } + return s.verifyStateUpdateRoot(update.OldRoot) +} + +func (s *State) purgeNoClassContracts() error { // purge noClassContracts // // As noClassContracts are not in StateDiff.DeployedContracts we can only purge them if their storage no longer exists. // Updating contracts with reverse diff will eventually lead to the deletion of noClassContract's storage key from db. Thus, // we can use the lack of key's existence as reason for purging noClassContracts. - for addr := range noClassContracts { noClassC, err := NewContractUpdater(&addr, s.txn) if err != nil { @@ -596,8 +603,7 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { } } } - - return s.verifyStateUpdateRoot(update.OldRoot) + return nil } func (s *State) removeDeclaredClasses(blockNumber uint64, v0Classes []*felt.Felt, v1Classes map[felt.Felt]*felt.Felt) error { diff --git a/node/node.go b/node/node.go index 911ced58c2..d3610b5df0 100644 --- a/node/node.go +++ b/node/node.go @@ -369,7 +369,10 @@ func (n *Node) Run(ctx context.Context) { if n.plugin != nil { go func() { <-ctx.Done() - n.plugin.Shutdown() + err := n.plugin.Shutdown() + if err != nil { + n.log.Errorw("Error while calling plugins Shutdown() function", "err", err) + } }() } diff --git a/sync/sync.go b/sync/sync.go index 8899c57a04..af0f80fbd5 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -3,7 +3,6 @@ package sync import ( "context" "errors" - "fmt" "runtime" "sync/atomic" "time" @@ -206,7 +205,6 @@ func (s *Synchronizer) handlePluginRevertBlock() { reverseStateDiff, err := s.blockchain.GetReverseStateDiff() if err != nil { s.log.Warnw("Failed to retrieve reverse state diff", "head", fromBlock.Number, "hash", fromBlock.Hash.ShortString(), "err", err) - fmt.Println("werwerwer", err) return } var toBlock *core.Block From 0e4cbff1bfdcf7d16d7be2f83379d62ac9d475b0 Mon Sep 17 00:00:00 2001 From: rian Date: Mon, 30 Sep 2024 16:48:06 +0300 Subject: [PATCH 13/21] unexport performStateDeletions --- core/state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state.go b/core/state.go index 9e55540774..11dd37e445 100644 --- a/core/state.go +++ b/core/state.go @@ -545,7 +545,7 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { return fmt.Errorf("error getting reverse state diff: %v", err) } - err = s.PerformStateDeletions(blockNumber, update.StateDiff) + err = s.performStateDeletions(blockNumber, update.StateDiff) if err != nil { return fmt.Errorf("error performing state deletions: %v", err) } @@ -719,7 +719,7 @@ func (s *State) GetReverseStateDiff(blockNumber uint64, diff *StateDiff) (*State return &reversed, nil } -func (s *State) PerformStateDeletions(blockNumber uint64, diff *StateDiff) error { +func (s *State) performStateDeletions(blockNumber uint64, diff *StateDiff) error { // storage diffs for addr, storageDiffs := range diff.StorageDiffs { for key := range storageDiffs { From 8f42cb888af1622cb4a0553e68610235fddb43cd Mon Sep 17 00:00:00 2001 From: rian Date: Thu, 3 Oct 2024 12:55:52 +0300 Subject: [PATCH 14/21] address comments --- cmd/juno/juno.go | 2 +- sync/sync.go | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/cmd/juno/juno.go b/cmd/juno/juno.go index 1c8e046196..6739a63357 100644 --- a/cmd/juno/juno.go +++ b/cmd/juno/juno.go @@ -172,7 +172,7 @@ const ( "The upper limit is 4 million steps, and any higher value will still be capped at 4 million." corsEnableUsage = "Enable CORS on RPC endpoints" versionedConstantsFileUsage = "Use custom versioned constants from provided file" - pluginPathUsage = "Path to the plugins .so file" + pluginPathUsage = "Path to the plugin .so file" ) var Version string diff --git a/sync/sync.go b/sync/sync.go index af0f80fbd5..b8a4132542 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -283,7 +283,7 @@ func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stat s.log.Infow("Stored Block", "number", block.Number, "hash", block.Hash.ShortString(), "root", block.GlobalStateRoot.ShortString()) if s.plugin != nil { - err := (s.plugin).NewBlock(block, stateUpdate, newClasses) + err := s.plugin.NewBlock(block, stateUpdate, newClasses) if err != nil { s.log.Errorw("Plugin NewBlock failure:", err) } @@ -372,13 +372,8 @@ func (s *Synchronizer) revertHead(forkBlock *core.Block) { if err == nil { localHead = head.Hash } - s.log.Infow("Reorg detected", "localHead", localHead, "forkHead", forkBlock.Hash) - if err != nil { - s.log.Warnw("Failed getting reverse state-diff, err: ", err) - } - err = s.blockchain.RevertHead() - if err != nil { + if err := s.blockchain.RevertHead(); err != nil { s.log.Warnw("Failed reverting HEAD", "reverted", localHead, "err", err) } else { s.log.Infow("Reverted HEAD", "reverted", localHead) From f5b7b05dce8203ecc2505adfccfc6e66ccba251f Mon Sep 17 00:00:00 2001 From: rian Date: Thu, 3 Oct 2024 12:59:47 +0300 Subject: [PATCH 15/21] update doc --- docs/docs/plugin.md | 4 ++-- plugin/plugin.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/plugin.md b/docs/docs/plugin.md index 197cc0196b..07a3a64b92 100644 --- a/docs/docs/plugin.md +++ b/docs/docs/plugin.md @@ -16,7 +16,7 @@ Your plugin must implement the `JunoPlugin` interface, which includes methods fo ```go type JunoPlugin interface { Init() error - Shutdown() error // Note: Currently this function will never be called. + Shutdown() error NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error } @@ -25,7 +25,7 @@ type JunoPlugin interface { **Init**: Called when the plugin is initialized. This can be used to set up database connections or any other necessary resources. -**Shutdown**: Called when the plugin is shut down, though this function is currently never called by Juno. This can be used to clean up resources like database connections. +**Shutdown**: Called when the Juno node is shut down. This can be used to clean up resources like database connections. **NewBlock**: Triggered when a new block is synced by the Juno client. Juno will send the block, the corresponding state update, and any new classes. Importantly, Juno waits for the plugin to finish processing this function call before continuing. This ensures that the plugin completes its task before Juno proceeds with the blockchain sync. diff --git a/plugin/plugin.go b/plugin/plugin.go index 5ccf984e2d..7431490033 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -11,7 +11,7 @@ import ( //go:generate mockgen -destination=../mocks/mock_plugin.go -package=mocks github.com/NethermindEth/juno/plugin JunoPlugin type JunoPlugin interface { Init() error - Shutdown() error // Todo: Currently this function will never be called. + Shutdown() error NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error // The state is reverted by applying a write operation with the reverseStateDiff's StorageDiffs, Nonces, and ReplacedClasses, // and a delete option with its DeclaredV0Classes, DeclaredV1Classes, and ReplacedClasses. From e38761474ec3d4d191fc9a27599476db729219dc Mon Sep 17 00:00:00 2001 From: LordGhostX Date: Tue, 8 Oct 2024 12:27:47 +0100 Subject: [PATCH 16/21] update plugin docs --- docs/docs/{plugin.md => plugins.md} | 53 +++++++++++++---------------- docs/sidebars.js | 1 + 2 files changed, 25 insertions(+), 29 deletions(-) rename docs/docs/{plugin.md => plugins.md} (61%) diff --git a/docs/docs/plugin.md b/docs/docs/plugins.md similarity index 61% rename from docs/docs/plugin.md rename to docs/docs/plugins.md index 07a3a64b92..816f028906 100644 --- a/docs/docs/plugin.md +++ b/docs/docs/plugins.md @@ -2,27 +2,23 @@ title: Juno Plugins --- -## Overview +Juno supports plugins that satisfy the `JunoPlugin` interface, enabling developers to extend and customize Juno's behaviour and functionality by dynamically loading external plugins during runtime. -The Juno client now supports plugins that satisfy the `JunoPlugin` interface. This enhancement allows developers to extend and customize Juno's behavior by dynamically loading external plugins at runtime. +The `JunoPlugin` interface provides a structured way for plugins to interact with the blockchain by sending notifications when new blocks are added or reverted. This ensures state consistency, especially during blockchain reorganizations, while abstracting away the complexity of implementing block syncing and revert logic. -The `JunoPlugin` interface provides a structured way for plugins to interact with the blockchain by receiving notifications when new blocks are added or reverted. This ensures state consistency, especially during blockchain reorganizations, while abstracting away the complexity of implementing block syncing and revert logic. - -## The JunoPlugin Interface +## JunoPlugin Interface Your plugin must implement the `JunoPlugin` interface, which includes methods for initializing, shutting down, and handling new and reverted blocks. - ```go type JunoPlugin interface { Init() error - Shutdown() error + Shutdown() error NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error RevertBlock(from, to *BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error } ``` - **Init**: Called when the plugin is initialized. This can be used to set up database connections or any other necessary resources. **Shutdown**: Called when the Juno node is shut down. This can be used to clean up resources like database connections. @@ -36,7 +32,7 @@ type JunoPlugin interface { Here is a basic example of a plugin that satisfies the `JunoPlugin` interface: ```go -//go:generate go build -buildmode=plugin -o ../../build/plugin.so ./example.go +// go:generate go build -buildmode=plugin -o ../../build/plugin.so ./example.go type examplePlugin string // Important: "JunoPluginInstance" needs to be exported for Juno to load the plugin correctly @@ -45,43 +41,42 @@ var JunoPluginInstance examplePlugin var _ junoplugin.JunoPlugin = (*examplePlugin)(nil) func (p *examplePlugin) Init() error { - fmt.Println("ExamplePlugin initialized") - return nil + fmt.Println("ExamplePlugin initialized") + return nil } func (p *examplePlugin) Shutdown() error { - fmt.Println("ExamplePlugin shutdown") - return nil + fmt.Println("ExamplePlugin shutdown") + return nil } func (p *examplePlugin) NewBlock(block *core.Block, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class) error { - fmt.Println("ExamplePlugin NewBlock called") - return nil + fmt.Println("ExamplePlugin NewBlock called") + return nil } func (p *examplePlugin) RevertBlock(from, to *junoplugin.BlockAndStateUpdate, reverseStateDiff *core.StateDiff) error { - fmt.Println("ExamplePlugin RevertBlock called") - return nil + fmt.Println("ExamplePlugin RevertBlock called") + return nil } ``` -The ```JunoPluginInstance``` variable must be exported for Juno to correctly load the plugin: -```var JunoPluginInstance examplePlugin``` - -We ensure the plugin implements the ```JunoPlugin``` interface, with the following line: -```var _ junoplugin.JunoPlugin = (*examplePlugin)(nil)``` - +The `JunoPluginInstance` variable must be exported for Juno to correctly load the plugin: +`var JunoPluginInstance examplePlugin` +We ensure the plugin implements the `JunoPlugin` interface, with the following line: +`var _ junoplugin.JunoPlugin = (*examplePlugin)(nil)` -# Building and Loading the Plugin +## Building and loading the plugin Once you have written your plugin, you can compile it into a shared object file (.so) using the following command: -```go build -buildmode=plugin -o ./plugin.so /path/to/your/plugin.go``` -This command compiles the plugin into a shared object file (```plugin.so```), which can then be loaded by the Juno client using the ```--plugin-path``` flag. - -# Running Juno with the plugin +```shell +go build -buildmode=plugin -o ./plugin.so /path/to/your/plugin.go +``` -Once your plugin has been compiled into a `.so` file, you can run Juno with your plugin by providing the `--plugin-path` flag. This flag tells Juno where to find and load your plugin at runtime. +This command compiles the plugin into a shared object file (`plugin.so`), which can then be loaded by the Juno client using the `--plugin-path` flag. +## Running Juno with the plugin +Once your plugin has been compiled into a `.so` file, you can run Juno with your plugin by providing the `--plugin-path` flag. This flag tells Juno where to find and load your plugin at runtime. diff --git a/docs/sidebars.js b/docs/sidebars.js index 10125a3020..b98fd7bacf 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -12,6 +12,7 @@ const sidebars = { "hardware-requirements", "running-juno", "configuring", + "plugins", "running-on-gcp", "updating", ], From 1d57be8fd7b45fed330885c5eb77e1fe41f51451 Mon Sep 17 00:00:00 2001 From: rian Date: Fri, 11 Oct 2024 16:17:25 +0300 Subject: [PATCH 17/21] address some comments --- core/state.go | 10 ++++------ sync/sync.go | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/core/state.go b/core/state.go index 11dd37e445..effde8b518 100644 --- a/core/state.go +++ b/core/state.go @@ -563,10 +563,6 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { return err } - if err = s.purgeNoClassContracts(); err != nil { - return err - } - // purge deployed contracts for addr := range update.StateDiff.DeployedContracts { if err = s.purgeContract(&addr); err != nil { @@ -574,12 +570,14 @@ func (s *State) Revert(blockNumber uint64, update *StateUpdate) error { } } + if err = s.purgeNoClassContracts(); err != nil { + return err + } + return s.verifyStateUpdateRoot(update.OldRoot) } func (s *State) purgeNoClassContracts() error { - // purge noClassContracts - // // As noClassContracts are not in StateDiff.DeployedContracts we can only purge them if their storage no longer exists. // Updating contracts with reverse diff will eventually lead to the deletion of noClassContract's storage key from db. Thus, // we can use the lack of key's existence as reason for purging noClassContracts. diff --git a/sync/sync.go b/sync/sync.go index b8a4132542..7f548ae643 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -189,9 +189,6 @@ func (s *Synchronizer) fetchUnknownClasses(ctx context.Context, stateUpdate *cor } func (s *Synchronizer) handlePluginRevertBlock() { - if s.plugin == nil { - return - } fromBlock, err := s.blockchain.Head() if err != nil { s.log.Warnw("Failed to retrieve the reverted blockchain head block for the plugin", "err", err) @@ -207,23 +204,27 @@ func (s *Synchronizer) handlePluginRevertBlock() { s.log.Warnw("Failed to retrieve reverse state diff", "head", fromBlock.Number, "hash", fromBlock.Hash.ShortString(), "err", err) return } - var toBlock *core.Block - var toSU *core.StateUpdate + + var toBlockAndStateUpdate *junoplugin.BlockAndStateUpdate if fromBlock.Number != 0 { - toBlock, err = s.blockchain.BlockByHash(fromBlock.ParentHash) + toBlock, err := s.blockchain.BlockByHash(fromBlock.ParentHash) if err != nil { s.log.Warnw("Failed to retrieve the parent block for the plugin", "err", err) return } - toSU, err = s.blockchain.StateUpdateByNumber(toBlock.Number) + toSU, err := s.blockchain.StateUpdateByNumber(toBlock.Number) if err != nil { s.log.Warnw("Failed to retrieve the parents state-update for the plugin", "err", err) return } + toBlockAndStateUpdate = &junoplugin.BlockAndStateUpdate{ + Block: toBlock, + StateUpdate: toSU, + } } err = (s.plugin).RevertBlock( &junoplugin.BlockAndStateUpdate{Block: fromBlock, StateUpdate: fromSU}, - &junoplugin.BlockAndStateUpdate{Block: toBlock, StateUpdate: toSU}, + toBlockAndStateUpdate, reverseStateDiff) if err != nil { s.log.Errorw("Plugin RevertBlock failure:", "err", err) @@ -255,7 +256,9 @@ func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stat // revert the head and restart the sync process, hoping that the reorg is not deep // if the reorg is deeper, we will end up here again and again until we fully revert reorged // blocks - s.handlePluginRevertBlock() + if s.plugin != nil { + s.handlePluginRevertBlock() + } s.revertHead(block) } else { s.log.Warnw("Failed storing Block", "number", block.Number, From 7ae13439ab9ca084b7b4eb73cea4e042add85000 Mon Sep 17 00:00:00 2001 From: rian Date: Fri, 11 Oct 2024 16:26:48 +0300 Subject: [PATCH 18/21] fix test --- plugin/plugin_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 11389c2c1a..54f9cffe17 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -59,7 +59,7 @@ func TestPlugin(t *testing.T) { su0, block0, err := integGw.StateUpdateWithBlock(context.Background(), uint64(0)) require.NoError(t, err) plugin.EXPECT().RevertBlock(&junoplugin.BlockAndStateUpdate{block1, su1}, &junoplugin.BlockAndStateUpdate{block0, su0}, gomock.Any()) - plugin.EXPECT().RevertBlock(&junoplugin.BlockAndStateUpdate{block0, su0}, &junoplugin.BlockAndStateUpdate{nil, nil}, gomock.Any()) + plugin.EXPECT().RevertBlock(&junoplugin.BlockAndStateUpdate{block0, su0}, nil, gomock.Any()) for i := range 3 { su, block, err := mainGw.StateUpdateWithBlock(context.Background(), uint64(i)) require.NoError(t, err) From 61210af712bf8dc6dee6f1a633de176f634f4569 Mon Sep 17 00:00:00 2001 From: rian Date: Fri, 11 Oct 2024 16:49:15 +0300 Subject: [PATCH 19/21] upgrade plugin to service we block on plugin.Shutdown --- node/node.go | 19 ++++--------------- plugin/plugin.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/node/node.go b/node/node.go index d3610b5df0..bcd65175f0 100644 --- a/node/node.go +++ b/node/node.go @@ -103,8 +103,6 @@ type Node struct { log utils.Logger version string - - plugin junoplugin.JunoPlugin } // New sets the config and logger to the StarknetNode. @@ -161,13 +159,15 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen synchronizer := sync.New(chain, adaptfeeder.New(client), log, cfg.PendingPollInterval, dbIsRemote) gatewayClient := gateway.NewClient(cfg.Network.GatewayURL, log).WithUserAgent(ua).WithAPIKey(cfg.GatewayAPIKey) - var plugin junoplugin.JunoPlugin + pluginService := junoplugin.New(log) if cfg.PluginPath != "" { - plugin, err = junoplugin.Load(cfg.PluginPath) + plugin, err := junoplugin.Load(cfg.PluginPath) if err != nil { return nil, err } synchronizer.WithPlugin(plugin) + pluginService.WithPlugin(plugin) + services = append(services, pluginService) } var p2pService *p2p.Service @@ -271,7 +271,6 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen blockchain: chain, services: services, metricsService: metricsService, - plugin: plugin, } if n.cfg.EthNode == "" { @@ -366,16 +365,6 @@ func (n *Node) Run(ctx context.Context) { return } - if n.plugin != nil { - go func() { - <-ctx.Done() - err := n.plugin.Shutdown() - if err != nil { - n.log.Errorw("Error while calling plugins Shutdown() function", "err", err) - } - }() - } - for _, s := range n.services { wg.Go(func() { // Immediately acknowledge panicing services by shutting down the node diff --git a/plugin/plugin.go b/plugin/plugin.go index 7431490033..a807b88d72 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -1,13 +1,43 @@ package junoplugin import ( + "context" "fmt" "plugin" + "sync" "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/utils" ) +type PluginService struct { + plugin JunoPlugin + wg sync.WaitGroup + log utils.SimpleLogger +} + +func New(log utils.SimpleLogger) *PluginService { + return &PluginService{wg: sync.WaitGroup{}, log: log} +} + +func (p *PluginService) WithPlugin(plugin JunoPlugin) { + p.plugin = plugin +} + +func (p *PluginService) Run(ctx context.Context) error { + p.wg.Add(1) + go func() { + defer p.wg.Done() + <-ctx.Done() + if err := p.plugin.Shutdown(); err != nil { + p.log.Errorw("Error while calling plugin Shutdown() function", "err", err) + } + }() + p.wg.Wait() + return nil +} + //go:generate mockgen -destination=../mocks/mock_plugin.go -package=mocks github.com/NethermindEth/juno/plugin JunoPlugin type JunoPlugin interface { Init() error From 85aa88afe06028d2eacff055b1cae980887ef686 Mon Sep 17 00:00:00 2001 From: rian Date: Fri, 11 Oct 2024 16:54:46 +0300 Subject: [PATCH 20/21] lint --- plugin/plugin.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index a807b88d72..cb18f70343 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -12,17 +12,17 @@ import ( ) type PluginService struct { - plugin JunoPlugin - wg sync.WaitGroup - log utils.SimpleLogger + jPlugin JunoPlugin + wg sync.WaitGroup + log utils.SimpleLogger } func New(log utils.SimpleLogger) *PluginService { return &PluginService{wg: sync.WaitGroup{}, log: log} } -func (p *PluginService) WithPlugin(plugin JunoPlugin) { - p.plugin = plugin +func (p *PluginService) WithPlugin(jPlugin JunoPlugin) { + p.jPlugin = jPlugin } func (p *PluginService) Run(ctx context.Context) error { @@ -30,7 +30,7 @@ func (p *PluginService) Run(ctx context.Context) error { go func() { defer p.wg.Done() <-ctx.Done() - if err := p.plugin.Shutdown(); err != nil { + if err := p.jPlugin.Shutdown(); err != nil { p.log.Errorw("Error while calling plugin Shutdown() function", "err", err) } }() From 262dab45ddc8bf438fcdb21deba5b6e4f438f685 Mon Sep 17 00:00:00 2001 From: rian Date: Wed, 16 Oct 2024 15:26:17 +0300 Subject: [PATCH 21/21] remove brackets --- sync/sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/sync.go b/sync/sync.go index 7f548ae643..1270193d07 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -222,7 +222,7 @@ func (s *Synchronizer) handlePluginRevertBlock() { StateUpdate: toSU, } } - err = (s.plugin).RevertBlock( + err = s.plugin.RevertBlock( &junoplugin.BlockAndStateUpdate{Block: fromBlock, StateUpdate: fromSU}, toBlockAndStateUpdate, reverseStateDiff)