From c0bfdb3126f1eb54e93fae7c12c77f6b4460e96c Mon Sep 17 00:00:00 2001 From: charithabandi Date: Thu, 12 Dec 2024 18:10:59 -0600 Subject: [PATCH] Event broadcasting support: Ability to configure and run extensions Order Block transactions in nonce order and enforce consensus limits such as maxBlockSz, maxVotesPerTx Leader to induce ValidatorVoteBodies Tx if there are any events without resolutions Validators can broadcast their vote for the existing resolutions ethDeposits extension --- app/node/build.go | 38 +- app/node/node.go | 13 + config/config.go | 16 +- core/rpc/client/admin/jsonrpc/client.go | 1 - core/rpc/json/admin/responses.go | 1 - core/types/payload_test.go | 32 + core/types/payloads.go | 57 +- core/types/types.go | 1 - .../listeners/eth_deposits/deposits.go | 6 +- .../listeners/eth_deposits/deposits_test.go | 0 .../listeners/eth_deposits/ethereum.go | 4 +- go.mod | 20 +- go.sum | 102 +++ node/_statesync.go | 355 -------- node/block_processor/interfaces.go | 17 + node/block_processor/processor.go | 56 +- node/block_processor/transactions.go | 440 ++++++++++ node/block_processor/transactions_test.go | 808 ++++++++++++++++++ node/consensus/block.go | 2 +- node/consensus/blocksync.go | 2 +- node/consensus/engine.go | 9 +- node/consensus/engine_test.go | 46 +- node/consensus/interfaces.go | 4 + node/consensus/leader.go | 17 +- node/interfaces.go | 4 +- node/{_listeners => listeners}/mgr.go | 30 +- node/node.go | 9 +- node/node_live_test.go | 42 +- node/node_test.go | 8 +- node/services/jsonrpc/adminsvc/service.go | 9 +- node/txapp/routes_test.go | 5 - node/txapp/txapp.go | 13 +- node/voting/voting.go | 5 +- 33 files changed, 1724 insertions(+), 448 deletions(-) rename {_previous/extensions => extensions}/listeners/eth_deposits/deposits.go (98%) rename {_previous/extensions => extensions}/listeners/eth_deposits/deposits_test.go (100%) rename {_previous/extensions => extensions}/listeners/eth_deposits/ethereum.go (98%) delete mode 100644 node/_statesync.go create mode 100644 node/block_processor/transactions.go create mode 100644 node/block_processor/transactions_test.go rename node/{_listeners => listeners}/mgr.go (88%) diff --git a/app/node/build.go b/app/node/build.go index 8a25b1150..c7a0ffcc1 100644 --- a/app/node/build.go +++ b/app/node/build.go @@ -24,6 +24,7 @@ import ( blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" "github.com/kwilteam/kwil-db/node/consensus" "github.com/kwilteam/kwil-db/node/engine/execution" + "github.com/kwilteam/kwil-db/node/listeners" "github.com/kwilteam/kwil-db/node/mempool" "github.com/kwilteam/kwil-db/node/meta" "github.com/kwilteam/kwil-db/node/pg" @@ -71,7 +72,7 @@ func buildServer(ctx context.Context, d *coreDependencies) *server { accounts := buildAccountStore(ctx, d, db) // eventstore, votestore - _, vs := buildVoteStore(ctx, d, closers) // ev, vs + es, vs := buildVoteStore(ctx, d, closers) // ev, vs // TxAPP txApp := buildTxApp(ctx, d, db, accounts, vs, e) @@ -80,7 +81,7 @@ func buildServer(ctx context.Context, d *coreDependencies) *server { ss := buildSnapshotStore(d) // BlockProcessor - bp := buildBlockProcessor(ctx, d, db, txApp, accounts, vs, ss) + bp := buildBlockProcessor(ctx, d, db, txApp, accounts, vs, ss, es) // Consensus ce := buildConsensusEngine(ctx, d, db, mp, bs, bp, valSet) @@ -88,6 +89,9 @@ func buildServer(ctx context.Context, d *coreDependencies) *server { // Node node := buildNode(d, mp, bs, ce, ss, db) + // listeners + lm := buildListenerManager(d, es, txApp, node) + // RPC Services rpcSvcLogger := d.logger.New("USER") jsonRPCTxSvc := usersvc.NewService(db, e, node, bp, vs, rpcSvcLogger, @@ -135,6 +139,7 @@ func buildServer(ctx context.Context, d *coreDependencies) *server { closers: closers, node: node, ce: ce, + listeners: lm, jsonRPCServer: jsonRPCServer, jsonRPCAdminServer: jsonRPCAdminServer, dbCtx: db, @@ -202,17 +207,23 @@ func buildMetaStore(ctx context.Context, db *pg.DB) { } } +// service returns a common.Service with the given logger name +func (c *coreDependencies) service(loggerName string) *common.Service { + signer := auth.GetNodeSigner(c.privKey) + + return &common.Service{ + Logger: c.logger.New(loggerName), + GenesisConfig: c.genesisCfg, + LocalConfig: c.cfg, + Identity: signer.Identity(), + } +} + func buildTxApp(ctx context.Context, d *coreDependencies, db *pg.DB, accounts *accounts.Accounts, votestore *voting.VoteStore, engine *execution.GlobalContext) *txapp.TxApp { signer := auth.GetNodeSigner(d.privKey) - service := &common.Service{ - Logger: d.logger.New("TXAPP"), - Identity: signer.Identity(), - // TODO: pass extension configs - // ExtensionConfigs: make(map[string]map[string]string), - } - txapp, err := txapp.NewTxApp(ctx, db, engine, signer, nil, service, accounts, votestore) + txapp, err := txapp.NewTxApp(ctx, db, engine, signer, nil, d.service("TxAPP"), accounts, votestore) if err != nil { failBuild(err, "failed to create txapp") } @@ -220,8 +231,9 @@ func buildTxApp(ctx context.Context, d *coreDependencies, db *pg.DB, accounts *a return txapp } -func buildBlockProcessor(ctx context.Context, d *coreDependencies, db *pg.DB, txapp *txapp.TxApp, accounts *accounts.Accounts, vs *voting.VoteStore, ss *snapshotter.SnapshotStore) *blockprocessor.BlockProcessor { - bp, err := blockprocessor.NewBlockProcessor(ctx, db, txapp, accounts, vs, ss, d.genesisCfg, d.logger.New("BP")) +func buildBlockProcessor(ctx context.Context, d *coreDependencies, db *pg.DB, txapp *txapp.TxApp, accounts *accounts.Accounts, vs *voting.VoteStore, ss *snapshotter.SnapshotStore, es *voting.EventStore) *blockprocessor.BlockProcessor { + signer := auth.GetNodeSigner(d.privKey) + bp, err := blockprocessor.NewBlockProcessor(ctx, db, txapp, accounts, vs, ss, es, d.genesisCfg, signer, d.logger.New("BP")) if err != nil { failBuild(err, "failed to create block processor") } @@ -349,6 +361,10 @@ func buildSnapshotStore(d *coreDependencies) *snapshotter.SnapshotStore { return ss } +func buildListenerManager(d *coreDependencies, ev *voting.EventStore, txapp *txapp.TxApp, node *node.Node) *listeners.ListenerManager { + return listeners.NewListenerManager(d.service("ListenerManager"), ev, txapp, node) +} + func buildJRPCAdminServer(d *coreDependencies) *rpcserver.Server { var wantTLS bool addr := d.cfg.Admin.ListenAddress diff --git a/app/node/node.go b/app/node/node.go index 6cc83deec..d16067644 100644 --- a/app/node/node.go +++ b/app/node/node.go @@ -15,6 +15,7 @@ import ( "github.com/kwilteam/kwil-db/core/log" "github.com/kwilteam/kwil-db/node" "github.com/kwilteam/kwil-db/node/consensus" + "github.com/kwilteam/kwil-db/node/listeners" rpcserver "github.com/kwilteam/kwil-db/node/services/jsonrpc" "github.com/kwilteam/kwil-db/version" @@ -35,6 +36,7 @@ type server struct { // subsystems node *node.Node ce *consensus.ConsensusEngine + listeners *listeners.ListenerManager jsonRPCServer *rpcserver.Server jsonRPCAdminServer *rpcserver.Server } @@ -164,6 +166,17 @@ func (s *server) Start(ctx context.Context) error { return nil }) + // Start listener manager + group.Go(func() error { + go func() { + <-groupCtx.Done() + s.log.Info("stop listeners") + s.listeners.Stop() + }() + return s.listeners.Start() + }) + s.log.Info("listener manager started") + // TODO: node is starting the consensus engine for ease of testing // Start the consensus engine diff --git a/config/config.go b/config/config.go index 7f4a99f1f..17066c367 100644 --- a/config/config.go +++ b/config/config.go @@ -156,6 +156,7 @@ func DefaultConfig() *Config { DiscoveryTimeout: Duration(30 * time.Second), MaxRetries: 3, }, + Extensions: make(map[string]map[string]string), } } @@ -170,13 +171,14 @@ type Config struct { // ProfileMode string `toml:"profile_mode"` // ProfileFile string `toml:"profile_file"` - P2P PeerConfig `toml:"p2p" comment:"P2P related configuration"` - Consensus ConsensusConfig `toml:"consensus" comment:"Consensus related configuration"` - DB DBConfig `toml:"db" comment:"DB (PostgreSQL) related configuration"` - RPC RPCConfig `toml:"rpc" comment:"User RPC service configuration"` - Admin AdminConfig `toml:"admin" comment:"Admin RPC service configuration"` - Snapshots SnapshotConfig `toml:"snapshots" comment:"Snapshot creation and provider configuration"` - StateSync StateSyncConfig `toml:"state_sync" comment:"Statesync configuration (vs block sync)"` + P2P PeerConfig `toml:"p2p" comment:"P2P related configuration"` + Consensus ConsensusConfig `toml:"consensus" comment:"Consensus related configuration"` + DB DBConfig `toml:"db" comment:"DB (PostgreSQL) related configuration"` + RPC RPCConfig `toml:"rpc" comment:"User RPC service configuration"` + Admin AdminConfig `toml:"admin" comment:"Admin RPC service configuration"` + Snapshots SnapshotConfig `toml:"snapshots" comment:"Snapshot creation and provider configuration"` + StateSync StateSyncConfig `toml:"state_sync" comment:"Statesync configuration (vs block sync)"` + Extensions map[string]map[string]string `toml:"extensions" comment:"extension configuration"` } // PeerConfig corresponds to the [peer] section of the config. diff --git a/core/rpc/client/admin/jsonrpc/client.go b/core/rpc/client/admin/jsonrpc/client.go index fb871fba5..d2f02a4b1 100644 --- a/core/rpc/client/admin/jsonrpc/client.go +++ b/core/rpc/client/admin/jsonrpc/client.go @@ -147,7 +147,6 @@ func (cl *Client) Status(ctx context.Context) (*adminTypes.Status, error) { Syncing: res.Sync.Syncing, }, Validator: &adminTypes.ValidatorInfo{ - Role: res.Validator.Role, PubKey: res.Validator.PubKey, Power: res.Validator.Power, }, diff --git a/core/rpc/json/admin/responses.go b/core/rpc/json/admin/responses.go index c1518291a..26f551700 100644 --- a/core/rpc/json/admin/responses.go +++ b/core/rpc/json/admin/responses.go @@ -45,7 +45,6 @@ type SyncInfo struct { type HealthResponse struct { Version string `json:"version"` Healthy bool `json:"healthy"` - Role string `json:"role"` PubKey types.HexBytes `json:"pubkey"` NumValidators int `json:"num_validators"` } diff --git a/core/types/payload_test.go b/core/types/payload_test.go index 91cabf5af..5cd617fba 100644 --- a/core/types/payload_test.go +++ b/core/types/payload_test.go @@ -59,3 +59,35 @@ func TestMarshalUnmarshalPayload(t *testing.T) { assert.Equal(t, tp.val, tp2.val) } + +func TestValidatorVoteBodyMarshalUnmarshal(t *testing.T) { + voteBody := &types.ValidatorVoteBodies{ + Events: []*types.VotableEvent{ + { + Type: "emptydata", + Body: []byte(""), + }, + { + Type: "test", + Body: []byte("test"), + }, + { + Type: "test2", + Body: []byte("random large data, random large data,random large data,random large data,random large data,random large data,random large data,random large data,random large data,random large data,random large data,random large data,random large data,"), + }, + }, + } + + data, err := voteBody.MarshalBinary() + require.NoError(t, err) + + voteBody2 := &types.ValidatorVoteBodies{} + err = voteBody2.UnmarshalBinary(data) + require.NoError(t, err) + + require.NotNil(t, voteBody2) + require.NotNil(t, voteBody2.Events) + require.Len(t, voteBody2.Events, 3) + + require.Equal(t, voteBody.Events, voteBody2.Events) +} diff --git a/core/types/payloads.go b/core/types/payloads.go index 7ee15101f..723ff4493 100644 --- a/core/types/payloads.go +++ b/core/types/payloads.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "encoding" "encoding/binary" "errors" @@ -606,7 +607,31 @@ type ValidatorVoteBodies struct { var _ Payload = (*ValidatorVoteBodies)(nil) func (v *ValidatorVoteBodies) MarshalBinary() ([]byte, error) { - return serialize.Encode(v) + buf := new(bytes.Buffer) + // Length of events (uint32) + if err := binary.Write(buf, binary.LittleEndian, uint32(len(v.Events))); err != nil { + return nil, err + } + for _, event := range v.Events { + // Length of event type (uint32) + if err := binary.Write(buf, binary.LittleEndian, uint32(len(event.Type))); err != nil { + return nil, err + } + // Event type + if _, err := buf.WriteString(event.Type); err != nil { + return nil, err + } + // Length of event body (uint32) + if err := binary.Write(buf, binary.LittleEndian, uint32(len(event.Body))); err != nil { + return nil, err + } + // Event body + if _, err := buf.Write(event.Body); err != nil { + return nil, err + } + } + + return buf.Bytes(), nil } func (v *ValidatorVoteBodies) Type() PayloadType { @@ -614,7 +639,35 @@ func (v *ValidatorVoteBodies) Type() PayloadType { } func (v *ValidatorVoteBodies) UnmarshalBinary(p0 []byte) error { - return serialize.Decode(p0, v) + buf := bytes.NewBuffer(p0) + var numEvents uint32 + if err := binary.Read(buf, binary.LittleEndian, &numEvents); err != nil { + return err + } + v.Events = make([]*VotableEvent, numEvents) + for i := range v.Events { + var eventTypeLen uint32 + if err := binary.Read(buf, binary.LittleEndian, &eventTypeLen); err != nil { + return err + } + eventType := make([]byte, eventTypeLen) + if _, err := buf.Read(eventType); err != nil { + return err + } + var eventBodyLen uint32 + if err := binary.Read(buf, binary.LittleEndian, &eventBodyLen); err != nil { + return err + } + eventBody := make([]byte, eventBodyLen) + if _, err := buf.Read(eventBody); err != nil { + return err + } + v.Events[i] = &VotableEvent{ + Type: string(eventType), + Body: eventBody, + } + } + return nil } // CreateResolution is a payload for creating a new resolution. diff --git a/core/types/types.go b/core/types/types.go index 928167444..d4441c387 100644 --- a/core/types/types.go +++ b/core/types/types.go @@ -40,7 +40,6 @@ type JoinRequest struct { } type Validator struct { - Role string `json:"role"` PubKey HexBytes `json:"pubkey"` Power int64 `json:"power"` } diff --git a/_previous/extensions/listeners/eth_deposits/deposits.go b/extensions/listeners/eth_deposits/deposits.go similarity index 98% rename from _previous/extensions/listeners/eth_deposits/deposits.go rename to extensions/listeners/eth_deposits/deposits.go index a22a885f5..a9139b694 100644 --- a/_previous/extensions/listeners/eth_deposits/deposits.go +++ b/extensions/listeners/eth_deposits/deposits.go @@ -37,7 +37,7 @@ func init() { // It will search for a local extension configuration named "eth_deposit". func Start(ctx context.Context, service *common.Service, eventStore listeners.EventStore) error { config := &EthDepositConfig{} - listenerConfig, ok := service.LocalConfig.AppConfig.Extensions[ListenerName] + listenerConfig, ok := service.LocalConfig.Extensions[ListenerName] if !ok { service.Logger.Warn("no eth_deposit configuration found, eth_deposit oracle will not start") return nil // no configuration, so we don't start the oracle @@ -72,7 +72,7 @@ func Start(ctx context.Context, service *common.Service, eventStore listeners.Ev if err != nil { return fmt.Errorf("failed to get current block height: %w", err) } - service.Logger.S.Infof("ETH best block: %v", currentHeight) + service.Logger.Infof("ETH best block: %v", currentHeight) if lastHeight > currentHeight-config.RequiredConfirmations { return fmt.Errorf("starting height is greater than the last confirmed eth block height") @@ -135,7 +135,7 @@ func Start(ctx context.Context, service *common.Service, eventStore listeners.Ev // height range. This means inserting any that have not already been processed // for broadcast in a Kwil vote ID / approval transaction, and then storing the // processed height. -func processEvents(ctx context.Context, from, to int64, client *ethClient, eventStore listeners.EventStore, logger log.SugaredLogger) error { +func processEvents(ctx context.Context, from, to int64, client *ethClient, eventStore listeners.EventStore, logger log.Logger) error { logs, err := client.GetCreditEventLogs(ctx, from, to) if err != nil { return fmt.Errorf("failed to get credit event logs: %w", err) diff --git a/_previous/extensions/listeners/eth_deposits/deposits_test.go b/extensions/listeners/eth_deposits/deposits_test.go similarity index 100% rename from _previous/extensions/listeners/eth_deposits/deposits_test.go rename to extensions/listeners/eth_deposits/deposits_test.go diff --git a/_previous/extensions/listeners/eth_deposits/ethereum.go b/extensions/listeners/eth_deposits/ethereum.go similarity index 98% rename from _previous/extensions/listeners/eth_deposits/ethereum.go rename to extensions/listeners/eth_deposits/ethereum.go index dc65203ca..6c0a0a973 100644 --- a/_previous/extensions/listeners/eth_deposits/ethereum.go +++ b/extensions/listeners/eth_deposits/ethereum.go @@ -46,12 +46,12 @@ var creditEventSignature ethcommon.Hash = crypto.Keccak256Hash([]byte("Credit(ad type ethClient struct { targetAddress ethcommon.Address maxRetries int64 - logger log.SugaredLogger + logger log.Logger client *ethclient.Client } // newEthClient creates a new ethereum client -func newEthClient(ctx context.Context, rpcurl string, maxRetries int64, targetAddress ethcommon.Address, logger log.SugaredLogger) (*ethClient, error) { +func newEthClient(ctx context.Context, rpcurl string, maxRetries int64, targetAddress ethcommon.Address, logger log.Logger) (*ethClient, error) { var client *ethclient.Client // I don't set the max retries here because this only gets run on startup diff --git a/go.mod b/go.mod index 8c2ec14c5..8ae995077 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.0 require ( github.com/dgraph-io/badger/v4 v4.3.1 + github.com/ethereum/go-ethereum v1.14.11 github.com/go-chi/chi/v5 v5.1.0 github.com/jackc/pglogrepl v0.0.0-20240307033717-828fbfe908e9 github.com/jackc/pgx/v5 v5.7.1 @@ -33,18 +34,30 @@ require ( ) require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect + github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/certgen v1.2.0 // indirect github.com/decred/dcrd/crypto/rand v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/decred/slog v1.2.0 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -68,14 +81,20 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/supranational/blst v0.3.13 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect gonum.org/v1/gonum v0.15.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) require ( @@ -90,7 +109,6 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/gosigar v0.14.3 // indirect - github.com/ethereum/go-ethereum v1.14.11 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index 1f8f1c42f..0bab6ea36 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,14 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= @@ -18,7 +26,13 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= +github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -37,6 +51,22 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= +github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -48,12 +78,18 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= +github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/certgen v1.2.0 h1:FF6XXV//5q38/c6QbGQdR35ZJz0GPIkejsZZU3oHuBQ= github.com/decred/dcrd/certgen v1.2.0/go.mod h1:LRh6dF2WPQeDA6QQSZE+SfK7AL6FuFtCRDHZf8DyGzg= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= @@ -83,8 +119,12 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.14.11 h1:8nFDCUUE67rPc6AKxFj7JKaOa2W/W1Rse3oS6LvvxEY= github.com/ethereum/go-ethereum v1.14.11/go.mod h1:+l/fr42Mma+xBnhefL/+z11/hcmJ2egl+ScIVPjhc7E= +github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A= +github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -97,6 +137,10 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= @@ -107,6 +151,9 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= @@ -116,10 +163,14 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -139,6 +190,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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= github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -153,12 +206,15 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20241017200806-017d972448fc h1:NGyrhhFhwvRAZg02jnYVg3GBQy0qGBKmFQJwaPmpmxs= github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -175,12 +231,18 @@ github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= @@ -271,6 +333,10 @@ github.com/kwilteam/kwil-db/core v0.3.1-0.20241212163115-7353f2761884 h1:R8nARxa github.com/kwilteam/kwil-db/core v0.3.1-0.20241212163115-7353f2761884/go.mod h1:v3YA0w26s82aSVWoZMFFxFD39Bqvy/8eHKLEZhn6bBQ= github.com/kwilteam/kwil-db/parse v0.3.1-0.20241212163115-7353f2761884 h1:DyPpPyiuO2MwBrQ1QPkILJXgzdmFjVlRlJerICSp6ks= github.com/kwilteam/kwil-db/parse v0.3.1-0.20241212163115-7353f2761884/go.mod h1:REQKF6pOCxTzY6WaZwRU+QWuIWsRmviYcSgqdKa5zfc= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -311,6 +377,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -333,8 +401,15 @@ github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dz github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -458,11 +533,15 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -499,6 +578,8 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -515,9 +596,22 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= +github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= @@ -527,6 +621,8 @@ github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -639,6 +735,7 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -651,6 +748,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -744,6 +842,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -760,5 +860,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/node/_statesync.go b/node/_statesync.go deleted file mode 100644 index 7eb8464c0..000000000 --- a/node/_statesync.go +++ /dev/null @@ -1,355 +0,0 @@ -package node - -import ( - "bytes" - "compress/gzip" - "context" - "crypto/sha256" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/kwilteam/kwil-db/core/log" - "github.com/kwilteam/kwil-db/node/types/sql" - "github.com/kwilteam/kwil-db/node/voting" -) - -const ( - ABCISnapshotQueryPath = "/snapshot/height" - ABCILatestSnapshotHeightPath = "/snapshot/latest" -) - -var ( - ErrStateSyncInProgress = errors.New("statesync already in progress") - ErrStateSyncNotInProgress = errors.New("statesync not in progress") - ErrRejectSnapshotChunk = errors.New("reject snapshot chunk") -) - -// StateSyncer is responsible for initializing the database state from the -// snapshots received from the network. It validates the snapshots against -// the trusted snapshot providers. -// The snapshot used to initialize are discarded after the database is restored. -// Snapshot store if enabled, only stores snapshots produced by the node itself. - -type StateSyncer struct { - // statesync configuration - dbConfig *DBConfig - - db sql.ReadTxMaker - - // directory to store snapshots and chunks - // same as the snapshot store directory - // as we allow to reuse the received snapshots - snapshotsDir string - - // trusted snapshot providers for verification - cometbft rfc servers - snapshotProviders []string - - // State syncer state - snapshot *Snapshot - chunks map[uint32]bool // Chunks received till now - rcvdChunks uint32 // Number of chunks received till now - - // Logger - log log.Logger -} - -// NewStateSyncer will initialize the state syncer that enables the node to -// receive and validate snapshots from the network and initialize the database state. -// It takes the database configuration, snapshot directory, and the trusted snapshot providers. -// Trusted snapshot providers are special nodes in the network trusted by the nodes and -// have snapshot creation enabled. These nodes are responsible for creating and validating snapshots. -func NewStateSyncer(ctx context.Context, cfg *DBConfig, snapshotDir string, providers []string, db sql.ReadTxMaker, logger log.Logger) (*StateSyncer, error) { - - ss := &StateSyncer{ - dbConfig: cfg, - db: db, - snapshotsDir: snapshotDir, - log: logger, - snapshotProviders: providers, - } - - // Ensure that the snapshot directory exists and is empty - if err := os.RemoveAll(snapshotDir); err != nil { - return nil, fmt.Errorf("failed to delete snapshot directory: %s with error: %w", snapshotDir, err) - } - if err := os.MkdirAll(snapshotDir, 0755); err != nil { - return nil, fmt.Errorf("failed to create snapshot directory: %s with error: %w", snapshotDir, err) - } - - return ss, nil -} - -// OfferSnapshot checks if the snapshot is valid and kicks off the state sync process -// accepted snapshot is stored on disk for later processing -func (ss *StateSyncer) OfferSnapshot(ctx context.Context, snapshot *Snapshot) error { - ss.log.Info("Offering snapshot", "height", snapshot.Height, "format", snapshot.Format, "chunk count", snapshot.ChunkCount, "hash", fmt.Sprintf("%x", snapshot.SnapshotHash)) - - // Check if we are already in the middle of a snapshot - if ss.snapshot != nil { - return ErrStateSyncInProgress - } - - // Validate the snapshot - err := ss.validateSnapshot(ctx, *snapshot) - if err != nil { - return err - } - - ss.snapshot = snapshot - ss.chunks = make(map[uint32]bool, snapshot.ChunkCount) - ss.rcvdChunks = 0 - return nil -} - -// ApplySnapshotChunk accepts a chunk and stores it on disk for later processing if valid -// If all chunks are received, it starts the process of restoring the database -func (ss *StateSyncer) ApplySnapshotChunk(ctx context.Context, chunk []byte, index uint32) (bool, error) { - if ss.snapshot == nil { - return false, ErrStateSyncNotInProgress - } - - // Check if the chunk has already been applied - if ss.chunks[index] { - return false, nil - } - - // Check if the chunk index is valid - if index >= ss.snapshot.ChunkCount { - ss.log.Error("Invalid chunk index", "index", index, "chunk-count", ss.snapshot.ChunkCount) - return false, ErrRejectSnapshotChunk - } - - // Validate the chunk hash - chunkHash := sha256.Sum256(chunk) - if chunkHash != ss.snapshot.ChunkHashes[index] { - return false, ErrRefetchSnapshotChunk - } - - // store the chunk on disk - chunkFileName := filepath.Join(ss.snapshotsDir, fmt.Sprintf("chunk-%d.sql.gz", index)) - err := os.WriteFile(chunkFileName, chunk, 0755) - if err != nil { - os.Remove(chunkFileName) - return false, errors.Join(err, ErrRetrySnapshotChunk) - } - - ss.log.Info("Applied snapshot chunk", log.Uint("height", ss.snapshot.Height), log.Uint("index", index)) - - ss.chunks[index] = true - ss.rcvdChunks++ - - // Kick off the process of restoring the database if all chunks are received - if ss.rcvdChunks == ss.snapshot.ChunkCount { - ss.log.Info("All chunks received - Starting DB restore process") - - // Ensure that the DB is empty before applying the snapshot - initialized, err := isDbInitialized(ctx, ss.db) - if err != nil { - ss.resetStateSync() - return false, errors.Join(err, ErrRejectSnapshot) - } - - if initialized { - ss.resetStateSync() - // Statesync is not allowed on an initialized DB - return false, errors.Join(ErrAbortSnapshotChunk, errors.New("postgres DB state is not empty, please reset the DB state before applying the snapshot")) - } - - // Restore the DB from the chunks - streamer := NewStreamer(ss.snapshot.ChunkCount, ss.snapshotsDir, ss.log) - defer streamer.Close() - reader, err := gzip.NewReader(streamer) - if err != nil { - ss.resetStateSync() - return false, errors.Join(err, ErrRejectSnapshot) - } - defer reader.Close() - - err = RestoreDB(ctx, reader, ss.dbConfig.DBName, ss.dbConfig.DBUser, - ss.dbConfig.DBPass, ss.dbConfig.DBHost, ss.dbConfig.DBPort, - ss.snapshot.SnapshotHash, ss.log) - if err != nil { - ss.resetStateSync() - return false, errors.Join(err, ErrRejectSnapshot) - } - ss.log.Info("DB restored") - - ss.chunks = nil - ss.rcvdChunks = 0 - ss.snapshot = nil - return true, nil - } - - return false, nil -} - -// RestoreDB restores the database from the logical sql dump using psql command -// It also validates the snapshot hash, before restoring the database -func RestoreDB(ctx context.Context, snapshot io.Reader, - dbName, dbUser, dbPass, dbHost, dbPort string, - snapshotHash []byte, logger log.Logger) error { - // unzip and stream the sql dump to psql - cmd := exec.CommandContext(ctx, - "psql", - "--username", dbUser, - "--host", dbHost, - "--port", dbPort, - "--dbname", dbName, - "--no-password", - ) - if dbPass != "" { - cmd.Env = append(os.Environ(), "PGPASSWORD="+dbPass) - } - - // cmd.Stdout = &stderr - stdinPipe, err := cmd.StdinPipe() // stdin for psql command - if err != nil { - return err - } - defer stdinPipe.Close() - - logger.Info("Restore DB: ", "command", cmd.String()) - - if err := cmd.Start(); err != nil { - return err - } - - // decompress the chunk streams and stream the sql dump to psql stdinPipe - if err := decompressAndValidateSnapshotHash(stdinPipe, snapshot, snapshotHash); err != nil { - return err - } - - stdinPipe.Close() // signifies the end of the input stream to the psql command - - if err := cmd.Wait(); err != nil { - return err - } - - return nil -} - -// decompressAndValidateChunkStreams decompresses the chunk streams and validates the snapshot hash -func decompressAndValidateSnapshotHash(output io.Writer, reader io.Reader, snapshotHash []byte) error { - hasher := sha256.New() - _, err := io.Copy(io.MultiWriter(output, hasher), reader) - if err != nil { - return fmt.Errorf("failed to decompress chunk streams: %w", err) - } - hash := hasher.Sum(nil) - - // Validate the hash of the decompressed chunks - if !bytes.Equal(hash, snapshotHash) { - return fmt.Errorf("invalid snapshot hash %x, expected %x", hash, snapshotHash) - } - return nil -} - -// validateSnapshot validates the snapshot against the trusted snapshot providers -func (ss *StateSyncer) validateSnapshot(ctx context.Context, snapshot Snapshot) error { - // Check if the snapshot's contents are valid - if snapshot.Format != DefaultSnapshotFormat { - return ErrUnsupportedSnapshotFormat - } - - if snapshot.Height <= 0 || snapshot.ChunkCount <= 0 || - snapshot.ChunkCount != uint32(len(snapshot.ChunkHashes)) { - return ErrInvalidSnapshot - } - - // Query the snapshot providers to check if the snapshot is valid - height := fmt.Sprintf("%d", snapshot.Height) - verified := false - for _, clt := range ss.snapshotProviders { - res, err := clt.ABCIQuery(ctx, ABCISnapshotQueryPath, []byte(height)) - if err != nil { - ss.log.Info("Failed to query snapshot", log.Error(err)) // failover to next provider - continue - } - - if len(res.Response.Value) > 0 { - var snap Snapshot - err = json.Unmarshal(res.Response.Value, &snap) - if err != nil { - ss.log.Error("Failed to unmarshal snapshot", log.Error(err)) - continue - } - - if snap.Height != snapshot.Height || snap.SnapshotSize != snapshot.SnapshotSize || - snap.ChunkCount != snapshot.ChunkCount || !bytes.Equal(snap.SnapshotHash, snapshot.SnapshotHash) { - ss.log.Error("Invalid snapshot", log.Uint("height", snapshot.Height), log.Any("Expected ", snap), log.Any("Actual", snapshot)) - break - } - - verified = true - break - } - } - - if !verified { - return ErrInvalidSnapshot - } - - return nil -} - -func (ss *StateSyncer) resetStateSync() { - ss.snapshot = nil - ss.chunks = nil - ss.rcvdChunks = 0 - - os.RemoveAll(ss.snapshotsDir) - os.MkdirAll(ss.snapshotsDir, 0755) -} - -// rpcClient sets up a new RPC client -func ChainRPCClient(server string) (*rpchttp.HTTP, error) { - if !strings.Contains(server, "://") { - server = "http://" + server - } - c, err := rpchttp.New(server, "/websocket") - if err != nil { - return nil, err - } - return c, nil -} - -// GetLatestSnapshotHeight queries the trusted snapshot providers to get the latest snapshot height. -func GetLatestSnapshotInfo(ctx context.Context, client cometClient.ABCIClient) (*Snapshot, error) { - res, err := client.ABCIQuery(ctx, ABCILatestSnapshotHeightPath, nil) - if err != nil { - return nil, err - } - - if len(res.Response.Value) == 0 { - return nil, errors.New("no snapshot found") - } - - var snap Snapshot - err = json.Unmarshal(res.Response.Value, &snap) - if err != nil { - return nil, err - } - - return &snap, nil -} - -func isDbInitialized(ctx context.Context, db sql.ReadTxMaker) (bool, error) { - tx, err := db.BeginReadTx(ctx) - if err != nil { - return false, err - } - defer tx.Rollback(ctx) - - vals, err := voting.GetValidators(ctx, tx) - if err != nil { - return false, err - } - - return len(vals) > 0, nil -} diff --git a/node/block_processor/interfaces.go b/node/block_processor/interfaces.go index 7c2894bab..fa42ee9f9 100644 --- a/node/block_processor/interfaces.go +++ b/node/block_processor/interfaces.go @@ -10,6 +10,7 @@ import ( "github.com/kwilteam/kwil-db/node/snapshotter" "github.com/kwilteam/kwil-db/node/txapp" "github.com/kwilteam/kwil-db/node/types/sql" + "github.com/kwilteam/kwil-db/node/voting" ) // DB is the interface for the main SQL database. All queries must be executed @@ -65,3 +66,19 @@ type SnapshotModule interface { Enabled() bool } + +// EventStore allows the BlockProcessor to read events from the event store. +type EventStore interface { + // GetUnbroadcastedEvents filters out the events observed by the validator + // that are not previously broadcasted. + GetUnbroadcastedEvents(ctx context.Context) ([]*types.UUID, error) + + // MarkBroadcasted marks list of events as broadcasted. + MarkBroadcasted(ctx context.Context, ids []*types.UUID) error +} + +var ( + // getEvents gets all events, even if they have been + // marked received + getEvents = voting.GetEvents +) diff --git a/node/block_processor/processor.go b/node/block_processor/processor.go index 3f4785742..a54180e27 100644 --- a/node/block_processor/processor.go +++ b/node/block_processor/processor.go @@ -11,6 +11,7 @@ import ( "slices" "sort" "sync" + "sync/atomic" "github.com/kwilteam/kwil-db/common" "github.com/kwilteam/kwil-db/config" @@ -35,11 +36,12 @@ import ( type BlockProcessor struct { // config genesisParams *config.GenesisConfig + signer auth.Signer mtx sync.Mutex // mutex to protect the consensus params // consensus params appHash ktypes.Hash - height int64 + height atomic.Int64 chainCtx *common.ChainContext status *blockExecStatus @@ -54,10 +56,15 @@ type BlockProcessor struct { accounts Accounts validators ValidatorModule snapshotter SnapshotModule + events EventStore log log.Logger + + broadcastTxFn BroadcastTxFn } -func NewBlockProcessor(ctx context.Context, db DB, txapp TxApp, accounts Accounts, vs ValidatorModule, sp SnapshotModule, genesisCfg *config.GenesisConfig, logger log.Logger) (*BlockProcessor, error) { +type BroadcastTxFn func(ctx context.Context, tx *ktypes.Transaction, sync uint8) (*ktypes.ResultBroadcastTx, error) + +func NewBlockProcessor(ctx context.Context, db DB, txapp TxApp, accounts Accounts, vs ValidatorModule, sp SnapshotModule, es EventStore, genesisCfg *config.GenesisConfig, signer auth.Signer, logger log.Logger) (*BlockProcessor, error) { // get network parameters from the chain context bp := &BlockProcessor{ db: db, @@ -65,6 +72,7 @@ func NewBlockProcessor(ctx context.Context, db DB, txapp TxApp, accounts Account accounts: accounts, validators: vs, snapshotter: sp, + signer: signer, log: logger, } @@ -84,7 +92,7 @@ func NewBlockProcessor(ctx context.Context, db DB, txapp TxApp, accounts Account if err != nil { return nil, fmt.Errorf("failed to get chain state: %w", err) } - bp.height = height + bp.height.Store(height) copy(bp.appHash[:], appHash) networkParams, err := meta.LoadParams(ctx, tx) @@ -105,6 +113,10 @@ func NewBlockProcessor(ctx context.Context, db DB, txapp TxApp, accounts Account return bp, nil } +func (bp *BlockProcessor) SetBroadcastTxFn(fn BroadcastTxFn) { + bp.broadcastTxFn = fn +} + func (bp *BlockProcessor) Close() error { bp.mtx.Lock() defer bp.mtx.Unlock() @@ -131,7 +143,7 @@ func (bp *BlockProcessor) Rollback(ctx context.Context, height int64, appHash kt } // set the block proposer back to it's previous state - bp.height = height + bp.height.Store(height) bp.appHash = appHash readTx, err := bp.db.BeginReadTx(ctx) @@ -198,7 +210,7 @@ func (bp *BlockProcessor) CheckTx(ctx context.Context, tx *ktypes.Transaction, r Ctx: ctx, BlockContext: &common.BlockContext{ ChainContext: bp.chainCtx, - Height: bp.height + 1, // ?? TODO: can crash as CheckTX can happen parallel to block execution + Height: bp.height.Load() + 1, Proposer: bp.genesisParams.Leader, // always the leader? }, TxID: hex.EncodeToString(txHash[:]), @@ -260,7 +272,7 @@ func (bp *BlockProcessor) InitChain(ctx context.Context) (int64, []byte, error) return -1, nil, fmt.Errorf("genesis transaction commit failed: %w", err) } - bp.height = genCfg.InitialHeight + bp.height.Store(genCfg.InitialHeight) copy(bp.appHash[:], genCfg.StateHash) bp.chainCtx.NetworkParameters = networkParams @@ -353,6 +365,13 @@ func (bp *BlockProcessor) ExecuteBlock(ctx context.Context, req *ktypes.BlockExe // record the end time of the block execution bp.recordBlockExecEndTime() + // Broadcast any voteID events that have not been broadcasted yet + if bp.broadcastTxFn != nil { + if err = bp.BroadcastVoteIDTx(ctx, bp.consensusTx); err != nil { + return nil, fmt.Errorf("failed to broadcast the voteID transactions: %w", err) + } + } + _, err = bp.txapp.Finalize(ctx, bp.consensusTx, blockCtx) if err != nil { return nil, fmt.Errorf("failed to finalize the block execution: %w", err) @@ -447,13 +466,22 @@ func (bp *BlockProcessor) Commit(ctx context.Context, req *ktypes.CommitRequest) bp.clearBlockExecutionStatus() // TODO: not very sure where to clear this - bp.height = req.Height + bp.height.Store(req.Height) copy(bp.appHash[:], req.AppHash[:]) bp.log.Info("Committed Block", "height", req.Height, "appHash", req.AppHash.String()) return nil } +// This function enforces proper nonce ordering, validates transactions, and ensures +// that consensus limits such as the maximum block size, maxVotesPerTx are met. It also adds +// validator vote transactions for events observed by the leader. This function is +// used exclusively by the leader node to prepare the proposal block. +func (bp *BlockProcessor) PrepareProposal(ctx context.Context, txs [][]byte) (finalTxs [][]byte, invalidTxs [][]byte, err error) { + // unmarshal and index the transactions + return bp.prepareBlockTransactions(ctx, txs) +} + var ( statesyncSnapshotSchemas = []string{"kwild_voting", "kwild_internal", "kwild_chain", "kwild_accts", "kwild_migrations", "ds_*"} statsyncExcludedTables = []string{"kwild_internal.sentry"} @@ -572,3 +600,17 @@ func (bp *BlockProcessor) ConsensusParams() *ktypes.ConsensusParams { MigrationStatus: bp.chainCtx.NetworkParameters.MigrationStatus, } } + +func (bp *BlockProcessor) SetNetworkParameters(params *common.NetworkParameters) { + bp.mtx.Lock() + defer bp.mtx.Unlock() + + bp.chainCtx.NetworkParameters = &common.NetworkParameters{ + MaxBlockSize: params.MaxBlockSize, + JoinExpiry: params.JoinExpiry, + VoteExpiry: params.VoteExpiry, + DisabledGasCosts: params.DisabledGasCosts, + MaxVotesPerTx: params.MaxVotesPerTx, + MigrationStatus: params.MigrationStatus, + } +} diff --git a/node/block_processor/transactions.go b/node/block_processor/transactions.go new file mode 100644 index 000000000..7fad77497 --- /dev/null +++ b/node/block_processor/transactions.go @@ -0,0 +1,440 @@ +package blockprocessor + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "sort" + + "github.com/kwilteam/kwil-db/core/types" + ktypes "github.com/kwilteam/kwil-db/core/types" + "github.com/kwilteam/kwil-db/node/types/sql" +) + +// txSubList implements sort.Interface to perform in-place sorting of a slice +// that is a subset of another slice, reordering in both while staying within +// the subsets positions in the parent slice. +// +// For example: +// +// parent slice: {a0, b2, b0, a1, b1} +// b's subset: {b2, b0, b1} +// sorted subset: {b0, b1, b2} +// parent slice: {a0, b0, b1, a1, b2} +// +// The set if locations used by b elements within the parent slice is unchanged, +// but the elements are sorted. +type txSubList struct { + sub []*indexedTxn // sort.Sort references only this with Len and Less + super []*indexedTxn // sort.Sort also Swaps in super using the i field +} + +func (txl txSubList) Len() int { + return len(txl.sub) +} + +func (txl txSubList) Less(i int, j int) bool { + a, b := txl.sub[i], txl.sub[j] + return a.Body.Nonce < b.Body.Nonce +} + +func (txl txSubList) Swap(i int, j int) { + // Swap elements in sub. + txl.sub[i], txl.sub[j] = txl.sub[j], txl.sub[i] + // Swap the elements in their positions in super. + ip, jp := txl.sub[i].i, txl.sub[j].i + txl.super[ip], txl.super[jp] = txl.super[jp], txl.super[ip] +} + +// indexedTxn facilitates in-place sorting of transaction slices that are +// subsets of other larger slices using a txSubList. This is only used within +// prepareMempoolTxns, and is package-level rather than scoped to the function +// because we define methods to implement sort.Interface. +type indexedTxn struct { + i int // index in superset slice + *types.Transaction + + is int // not used for sorting, only referencing the marshalled txn slice +} + +// prepareBlockTransactions is used by the leader to prepare block transactions. +// It ensures nonce ordering, removes transactions from unfunded accounts, +// enforces block size limits, and applies the maxVotesPerTx limit for voteID transactions. +// Additionally, it includes the ValidatorVoteBody transaction for unresolved events. +// The final transaction order is: MempoolProposerTxns, ValidatorVoteBodyTx, Other MempoolTxns (Nonce ordered, stable sorted). +func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][]byte) (finalTxs [][]byte, invalidTxs [][]byte, err error) { + // Unmarshal and index the transactions. + var okTxns []*indexedTxn + invalidTxs = make([][]byte, 0, len(txs)) + var i int + + for is, tx := range txs { + txn := &types.Transaction{} + if err = txn.UnmarshalBinary(tx); err != nil { + invalidTxs = append(invalidTxs, tx) + bp.log.Error("Failed to unmarshal transaction that was previously accepted to mempool", "error", err) + continue + } + okTxns = append(okTxns, &indexedTxn{i, txn, is}) + i++ + } + + // Group by sender and stable sort each group by nonce. + grouped := make(map[string][]*indexedTxn) + for _, txn := range okTxns { + key := string(txn.Sender) + grouped[key] = append(grouped[key], txn) + } + + for _, group := range grouped { + sort.Stable(txSubList{group, okTxns}) + } + + nonces := make([]uint64, 0, len(okTxns)) + var propTxs, otherTxns []*indexedTxn + i = 0 + proposerNonce := uint64(0) + + readTx, err := bp.db.BeginReadTx(ctx) + if err != nil { + bp.log.Error("Failed to begin read transaction", "error", err) + return nil, nil, err + } + defer readTx.Rollback(ctx) + + // Enfore nonce ordering and remove transactions from the unfunded accounts + for _, tx := range okTxns { + if i > 0 && tx.Body.Nonce == nonces[i-1] && bytes.Equal(tx.Sender, okTxns[i-1].Sender) { + invalidTxs = append(invalidTxs, txs[tx.is]) + bp.log.Warn("Transaction has a duplicate nonce", "tx", tx) + continue + } + + // Enforce maxVptesPerTx limit for voteID transactions + if tx.Body.PayloadType == types.PayloadTypeValidatorVoteIDs { + voteIDs := &types.ValidatorVoteIDs{} + if err = voteIDs.UnmarshalBinary(tx.Body.Payload); err != nil { + invalidTxs = append(invalidTxs, txs[tx.is]) + bp.log.Warn("Dropping voteID tx: failed to unmarshal ValidatorVoteIDs transaction", "error", err) + continue + } + + if len(voteIDs.ResolutionIDs) > int(bp.chainCtx.NetworkParameters.MaxVotesPerTx) { + invalidTxs = append(invalidTxs, txs[tx.is]) + bp.log.Warn("Dropping voteID tx: exceeds max votes per tx", "numVotes", len(voteIDs.ResolutionIDs), + "maxVotes", bp.chainCtx.NetworkParameters.MaxVotesPerTx) + continue + } + } + + // Drop transactions from unfunded accounts in gasEnabled mode + if !bp.chainCtx.NetworkParameters.DisabledGasCosts { + balance, nonce, err := bp.AccountInfo(ctx, readTx, tx.Sender, false) + if err != nil { + bp.log.Error("failed to get account info", "error", err) + continue + } + + if nonce == 0 && balance.Sign() == 0 { + invalidTxs = append(invalidTxs, txs[tx.is]) + bp.log.Warn("Dropping tx from unfunded account while preparing the block", "account", hex.EncodeToString(tx.Sender)) + continue + } + } + + if bytes.Equal(tx.Sender, bp.signer.Identity()) { + proposerNonce = tx.Body.Nonce + propTxs = append(propTxs, tx) + } else { + otherTxns = append(otherTxns, tx) + } + nonces = append(nonces, tx.Body.Nonce) + i++ + } + + // Enforce block size limits + // Txs order: MempoolProposerTxns, ProposerInjectedTxns, MempoolTxns + + finalTxs = make([][]byte, 0) + maxTxBytes := bp.chainCtx.NetworkParameters.MaxBlockSize + + for _, tx := range propTxs { + txBts := txs[tx.is] + txSize := int64(len(txBts)) + if maxTxBytes < txSize { + break + } + maxTxBytes -= txSize + finalTxs = append(finalTxs, txBts) + } + + var voteBodyTx []byte // TODO: check proposerNonce value again + voteBodyTx, err = bp.prepareValidatorVoteBodyTx(ctx, int64(proposerNonce)+1, maxTxBytes) + if err != nil { + bp.log.Error("Failed to prepare validator vote body transaction", "error", err) + return nil, nil, err + } + if voteBodyTx != nil { + finalTxs = append(finalTxs, voteBodyTx) + maxTxBytes -= int64(len(voteBodyTx)) + } + + // senders tracks the sender of transactions that has pushed over the bytes limit for the block. + // If a sender is in the senders, skip all subsequent transactions from the sender + // because nonces need to be sequential. + // Keep checking transactions for other senders that may be smaller and fit in the remaining space. + senders := make(map[string]bool) + for _, tx := range otherTxns { + sender := string(tx.Sender) + // if the sender is already in the skipped senders, skip the transaction + if _, ok := senders[sender]; ok { + continue + } + + txSize := int64(len(txs[tx.is])) + if maxTxBytes < txSize { + // Ignore the transaction and all subsequent transactions from the sender + senders[sender] = true + continue + } + + maxTxBytes -= txSize + finalTxs = append(finalTxs, txs[tx.is]) + } + + return finalTxs, invalidTxs, err +} + +// prepareValidatorVoteBodyTx authors the ValidatorVoteBody transaction to be included by the leader in the block. +// It fetches the events which does not have resolutions yet and creates a validator vote body transaction. +// The number of events to be included in a single transaction is limited either by MaxVotesPerTx or the maxTxSize +// whichever is reached first. The estimated fee for validatorVOteBodies transaction is directly proportional to +// the size of the event body. The transaction is signed by the leader and returned. +func (bp *BlockProcessor) prepareValidatorVoteBodyTx(ctx context.Context, nonce int64, maxTxSize int64) ([]byte, error) { + readTx, err := bp.db.BeginReadTx(ctx) + if err != nil { + return nil, err + } + defer readTx.Rollback(ctx) + + bal, n, err := bp.AccountInfo(ctx, readTx, bp.signer.Identity(), false) + if err != nil { + return nil, err + } + + events, err := getEvents(ctx, readTx) + if err != nil { + return nil, err + } + + if len(events) == 0 { + bp.log.Debug("No events to propose for voting") + return nil, nil + } + + // If gas costs are enabled, ensure that the node has sufficient funds to include this transaction + if !bp.chainCtx.NetworkParameters.DisabledGasCosts && n == 0 && bal.Sign() == 0 { + bp.log.Debug("Leader account has no balance, not allowed to propose any transactions") + return nil, nil + } + + ids := make([]*types.UUID, 0, len(events)) + for _, e := range events { + ids = append(ids, e.ID()) + } + + // Limit only upto MaxVoteIDsPerTx events to be included in a single transaction + if len(ids) > int(bp.chainCtx.NetworkParameters.MaxVotesPerTx) { + ids = ids[:bp.chainCtx.NetworkParameters.MaxVotesPerTx] + } + + eventMap := make(map[types.UUID]*types.VotableEvent) + for _, evt := range events { + eventMap[*evt.ID()] = evt + } + + emptyTxSz, err := bp.emptyVodeBodyTxSize() + if err != nil { + return nil, err + } + maxTxSize -= emptyTxSz + + var finalEvents []*types.VotableEvent + estimatedTxSize := 0 + + for _, id := range ids { + evt, ok := eventMap[*id] + if !ok { + bp.log.Error("Event not found in event map", "eventID", id) + return nil, fmt.Errorf("event %s not found in event map", id.String()) + } + + evtSz := 4 + 4 + len(evt.Type) + len(evt.Body) + estimatedTxSize = evtSz + if int64(evtSz) > maxTxSize { + bp.log.Debug("reached maximum proposer tx size", "maxTxSize", maxTxSize, "evtSz", evtSz) + break + } + + maxTxSize -= int64(evtSz) + finalEvents = append(finalEvents, &types.VotableEvent{ + Type: evt.Type, + Body: evt.Body, + }) + } + + if len(finalEvents) == 0 && len(ids) > 0 { + bp.log.Warn("found proposer events to propose, but cannot fit them in a block", "maxTxSize", maxTxSize, + "numEvents", len(ids), "maxVotesPerTx", bp.chainCtx.NetworkParameters.MaxVotesPerTx, + "estimatedTxSize", estimatedTxSize) + return nil, nil + } + + tx, err := types.CreateTransaction(&types.ValidatorVoteBodies{ + Events: finalEvents, + }, bp.chainCtx.ChainID, uint64(nonce)) + if err != nil { + return nil, err + } + + // Fee estimation + fee, err := bp.Price(ctx, readTx, tx) + if err != nil { + return nil, err + } + tx.Body.Fee = fee + + if err = tx.Sign(bp.signer); err != nil { + return nil, err + } + + bts, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + + bp.log.Info("Created a ValidatorVoteBody transaction", "events", len(finalEvents), "nonce", n, "GasPrice", fee.String()) + + return bts, nil +} + +// emptyVodeBodyTxSize returns the size of an empty validator vote body transaction. +// used to estimate the size of the validator vote body transactions with events as the +// size is directly proportional to the events size. +func (bp *BlockProcessor) emptyVodeBodyTxSize() (int64, error) { + payload := &types.ValidatorVoteBodies{ + Events: []*types.VotableEvent{}, + } + tx, err := types.CreateTransaction(payload, bp.chainCtx.ChainID, 0) + if err != nil { + return -1, err + } + + err = tx.Sign(bp.signer) + if err != nil { + return -1, err + } + + bts, err := tx.MarshalBinary() + if err != nil { + return -1, err + } + + return int64(len(bts)), nil +} + +func (bp *BlockProcessor) BroadcastVoteIDTx(ctx context.Context, db sql.DB) error { + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + if err != nil { + return err + } + + if tx == nil || len(ids) == 0 { // no voteIDs to broadcast + return nil + } + + _, err = bp.broadcastTxFn(ctx, tx, 0) + if err != nil { + return err + } + + return bp.events.MarkBroadcasted(ctx, ids) +} + +func (bp *BlockProcessor) PrepareValidatorVoteIDTx(ctx context.Context, db sql.DB) (*ktypes.Transaction, []*types.UUID, error) { + readTx, err := bp.db.BeginReadTx(ctx) + if err != nil { + bp.log.Error("Failed to begin read transaction while preparing voteID Tx", "error", err) + return nil, nil, err + } + defer readTx.Rollback(ctx) + + // Only validators can issue voteID transactions not the leader or sentry nodes + + // check if the node is a leader + if bytes.Equal(bp.signer.Identity(), bp.genesisParams.Leader) { + bp.log.Debug("Leader node is not allowed to propose voteID transactions") + return nil, nil, nil + } + + // check if the node is a sentry node + vals := bp.GetValidators() + found := false + for _, val := range vals { + if bytes.Equal(val.PubKey, bp.signer.Identity()) { + found = true + } + } + + if !found { + bp.log.Debug("Sentry node is not allowed to propose voteID transactions") + return nil, nil, nil + } + + // Vote only if the voter observed the event corresponding to the resolution. + // ids are the resolution ids that the validator witnessed the events for and can vote on. + ids, err := bp.events.GetUnbroadcastedEvents(ctx) + if err != nil { + return nil, nil, err + } + + if len(ids) == 0 { + bp.log.Debug("no voteIDs to broadcast") + return nil, nil, nil + } + + // consider only the first maxVoteIDsPerTx events, to limit the postgres access roundtrips per block execution. + if len(ids) > int(bp.chainCtx.NetworkParameters.MaxVotesPerTx) { + ids = ids[:bp.chainCtx.NetworkParameters.MaxVotesPerTx] + } + + bal, nonce, err := bp.AccountInfo(ctx, readTx, bp.signer.Identity(), true) + if err != nil { + return nil, nil, err + } + + tx, err := types.CreateTransaction(&types.ValidatorVoteIDs{ResolutionIDs: ids}, bp.chainCtx.ChainID, uint64(nonce)+1) + if err != nil { + return nil, nil, err + } + + // Fee estimation + fee, err := bp.Price(ctx, readTx, tx) + if err != nil { + return nil, nil, err + } + tx.Body.Fee = fee + + // check if the node has enough balance to propose the transaction + if bal.Cmp(fee) < 0 { + bp.log.Warnf("skipping voteID broadcast: not enough balance to pay for the tx fee, balance: %s, fee: %s", bal.String(), fee.String()) + return nil, nil, nil + } + + if err = tx.Sign(bp.signer); err != nil { + return nil, nil, err + } + + return tx, ids, nil +} diff --git a/node/block_processor/transactions_test.go b/node/block_processor/transactions_test.go new file mode 100644 index 000000000..68be4b5f3 --- /dev/null +++ b/node/block_processor/transactions_test.go @@ -0,0 +1,808 @@ +package blockprocessor + +import ( + "bytes" + "context" + "math/big" + "testing" + + "github.com/kwilteam/kwil-db/common" + "github.com/kwilteam/kwil-db/config" + "github.com/kwilteam/kwil-db/core/crypto" + "github.com/kwilteam/kwil-db/core/crypto/auth" + "github.com/kwilteam/kwil-db/core/log" + "github.com/kwilteam/kwil-db/core/types" + ktypes "github.com/kwilteam/kwil-db/core/types" + "github.com/kwilteam/kwil-db/node/txapp" + "github.com/kwilteam/kwil-db/node/types/sql" + "github.com/stretchr/testify/require" +) + +func marshalTx(t *testing.T, tx *types.Transaction) []byte { + b, err := tx.MarshalBinary() + if err != nil { + t.Fatalf("could not marshal transaction! %v", err) + } + return b +} + +func cloneTx(tx *types.Transaction) *types.Transaction { + sig := make([]byte, len(tx.Signature.Data)) + copy(sig, tx.Signature.Data) + sender := make([]byte, len(tx.Sender)) + copy(sender, tx.Sender) + body := *tx.Body // same nonce + body.Fee = big.NewInt(0).Set(tx.Body.Fee) + body.Payload = make([]byte, len(tx.Body.Payload)) + copy(body.Payload, tx.Body.Payload) + return &types.Transaction{ + Signature: &auth.Signature{ + Data: sig, + Type: tx.Signature.Type, + }, + Body: &body, + Serialization: tx.Serialization, + Sender: sender, + } +} + +func secp256k1Signer(t *testing.T) *auth.EthPersonalSigner { + privKey, _, err := crypto.GenerateSecp256k1Key(nil) + require.NoError(t, err) + + privKeyBytes := privKey.Bytes() + k, err := crypto.UnmarshalSecp256k1PrivateKey(privKeyBytes) + require.NoError(t, err) + + return &auth.EthPersonalSigner{Key: *k} +} + +func TestPrepareMempoolTxns(t *testing.T) { + // To make these tests deterministic, we manually craft certain misorderings + // and the known expected orderings. Also include some malformed + // transactions that fail to unmarshal, which really shouldn't happen if the + // initial check passed but there is graceful handling of this in the code. + + // tA is the template transaction. Several fields may not be nil because of + // a legacy RLP issue where objects may be encoded that cannot be decoded. + + bp := &BlockProcessor{ + db: &mockDB{}, + log: log.DiscardLogger, + signer: secp256k1Signer(t), + chainCtx: &common.ChainContext{ + ChainID: "test", + NetworkParameters: &common.NetworkParameters{ + MaxBlockSize: 6 * 1024 * 1024, + MaxVotesPerTx: 100, + DisabledGasCosts: true, + }, + }, + txapp: &mockTxApp{}, + } + + tA := &types.Transaction{ + Signature: &auth.Signature{ + Data: []byte{}, + Type: auth.Ed25519Auth, + }, + Body: &types.TransactionBody{ + Description: "t", + Payload: []byte(`x`), + Fee: big.NewInt(0), + Nonce: 0, + }, + Sender: []byte(`guy`), + } + tAb := marshalTx(t, tA) + + // same sender, incremented nonce + tB := cloneTx(tA) + tB.Body.Nonce++ + tBb := marshalTx(t, tB) + + nextTx := func(tx *types.Transaction) *types.Transaction { + tx2 := cloneTx(tx) + tx2.Body.Nonce++ + return tx2 + } + + // second party + tOtherSenderA := cloneTx(tA) + tOtherSenderA.Sender = []byte(`otherguy`) + tOtherSenderAb := marshalTx(t, tOtherSenderA) + + // Same nonce tx, different body (diff bytes) + tOtherSenderAbDup := cloneTx(tOtherSenderA) + tOtherSenderAbDup.Body.Description = "dup" // not "t" + tOtherSenderAbDupb := marshalTx(t, tOtherSenderAbDup) + + tOtherSenderB := nextTx(tOtherSenderA) + tOtherSenderBb := marshalTx(t, tOtherSenderB) + + tOtherSenderC := nextTx(tOtherSenderB) + tOtherSenderCb := marshalTx(t, tOtherSenderC) + + // proposer party + tProposer := cloneTx(tA) + tProposer.Sender = bp.signer.Identity() + tProposerb := marshalTx(t, tProposer) + + invalid := []byte{9, 90, 22} + + tests := []struct { + name string + txs [][]byte + want [][]byte + }{ + { + "empty", + [][]byte{}, + [][]byte{}, + }, + { + "one and only invalid", + [][]byte{invalid}, + [][]byte{}, + }, + { + "one of two invalid", + [][]byte{invalid, tBb}, + [][]byte{tBb}, + }, + { + "one valid", + [][]byte{tAb}, + [][]byte{tAb}, + }, + { + "two valid", + [][]byte{tAb, tBb}, + [][]byte{tAb, tBb}, + }, + { + "two valid misordered", + [][]byte{tBb, tAb}, + [][]byte{tAb, tBb}, + }, + { + "multi-party, one misordered, stable", + [][]byte{tOtherSenderAb, tBb, tOtherSenderBb, tAb}, + [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tBb}, + }, + { + "multi-party, one misordered, one dup nonce, stable", + [][]byte{tOtherSenderAb, tOtherSenderAbDupb, tBb, tAb}, + [][]byte{tOtherSenderAb, tAb, tBb}, + }, + { + "multi-party, both misordered, stable", + [][]byte{tOtherSenderBb, tBb, tOtherSenderAb, tAb}, + [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tBb}, + }, + { + "multi-party, both misordered, alt. stable", + [][]byte{tBb, tOtherSenderBb, tOtherSenderAb, tAb}, + [][]byte{tAb, tOtherSenderAb, tOtherSenderBb, tBb}, + }, + { + "multi-party, big, with invalid in middle", + [][]byte{tOtherSenderCb, tBb, invalid, tOtherSenderBb, tOtherSenderAb, tAb}, + [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + }, + { + "multi-party, big, already correct", + [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + }, + { + "multi-party,proposer in the last, reorder", + [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb, tProposerb}, + [][]byte{tProposerb, tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + }, + { + "multi-party,proposer in the middle, reorder", + [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tProposerb, tOtherSenderCb, tBb}, + [][]byte{tProposerb, tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + }, + } + + ctx := context.Background() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + getEvents = func(_ context.Context, _ sql.Executor) ([]*types.VotableEvent, error) { + return nil, nil + } + + got, _, err := bp.prepareBlockTransactions(ctx, tt.txs) + require.NoError(t, err) + if len(got) != len(tt.want) { + t.Errorf("got %d txns, expected %d", len(got), len(tt.want)) + } + for i, txi := range got { + if !bytes.Equal(txi, tt.want[i]) { + t.Errorf("mismatched tx %d", i) + } + } + }) + } +} + +var ( + evt1 = &types.VotableEvent{ + Type: "test", + Body: []byte("test"), + } + evt2 = &types.VotableEvent{ + Type: "test", + Body: []byte("test2"), + } + evt3 = &types.VotableEvent{ + Type: "test", + Body: []byte("test3"), + } +) + +func TestPrepareVoteIDTx(t *testing.T) { + leader := secp256k1Signer(t) + validator := secp256k1Signer(t) + sentry := secp256k1Signer(t) + valSet := []*ktypes.Validator{ + { + PubKey: validator.Identity(), + Power: 1, + }, + { + PubKey: leader.Identity(), + Power: 1, + }, + } + genCfg := config.DefaultGenesisConfig() + genCfg.Leader = leader.Identity() + + bp := &BlockProcessor{ + db: &mockDB{}, + log: log.DiscardLogger, + signer: secp256k1Signer(t), + chainCtx: &common.ChainContext{ + ChainID: "test", + NetworkParameters: &common.NetworkParameters{ + MaxBlockSize: 6 * 1024 * 1024, + MaxVotesPerTx: 100, + DisabledGasCosts: true, + }, + }, + txapp: &mockTxApp{}, + genesisParams: genCfg, + } + + testcases := []struct { + name string + signer auth.Signer + events []*types.VotableEvent + cleanup func() + fn func(context.Context, *BlockProcessor, sql.DB, *mockEventStore) error + }{ + { + name: "no voteIDs to broadcast", + events: []*types.VotableEvent{}, // no events + signer: validator, + fn: func(ctx context.Context, bp *BlockProcessor, db sql.DB, es *mockEventStore) error { + bp.signer = validator + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + require.NoError(t, err) + require.Nil(t, tx) + require.Nil(t, ids) + return nil + }, + }, + { + name: "leader not allowed to broadcast voteIDs", + events: []*types.VotableEvent{evt1, evt2}, + signer: leader, + fn: func(ctx context.Context, bp *BlockProcessor, db sql.DB, es *mockEventStore) error { + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + require.NoError(t, err) + require.Nil(t, tx) + require.Nil(t, ids) + return nil + }, + }, + { + name: "sentry node not allowed to broadcast voteIDs", + events: []*types.VotableEvent{evt1, evt2}, + signer: sentry, + fn: func(ctx context.Context, bp *BlockProcessor, db sql.DB, es *mockEventStore) error { + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + require.NoError(t, err) + require.Nil(t, tx) + require.Nil(t, ids) + return nil + }, + }, + { + name: "validator broadcasts voteIDs in gasless mode", + signer: validator, + events: []*types.VotableEvent{evt1, evt2}, + fn: func(ctx context.Context, bp *BlockProcessor, db sql.DB, es *mockEventStore) error { + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + require.NoError(t, err) + require.NotNil(t, tx) + require.NotNil(t, ids) + require.Len(t, ids, 2) + return nil + }, + }, + { + name: "insufficient gas to broadcast voteIDs", + signer: validator, + events: []*types.VotableEvent{evt1, evt2}, + fn: func(ctx context.Context, bp *BlockProcessor, db sql.DB, es *mockEventStore) error { + bp.chainCtx.NetworkParameters.DisabledGasCosts = false + // set price of tx high: 1000 + price = big.NewInt(1000) + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + + require.NoError(t, err) + require.Nil(t, tx) + require.Nil(t, ids) + return nil + }, + cleanup: func() { + price = big.NewInt(0) + bp.chainCtx.NetworkParameters.DisabledGasCosts = true + }, + }, + { + name: "validator has sufficient gas to broadcast voteIDs", + signer: validator, + events: []*types.VotableEvent{evt1, evt2}, + fn: func(ctx context.Context, bp *BlockProcessor, db sql.DB, es *mockEventStore) error { + bp.chainCtx.NetworkParameters.DisabledGasCosts = false + // set price of tx low: 1 + price = big.NewInt(1000) + accountBalance = big.NewInt(1000) + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + + require.NoError(t, err) + require.NotNil(t, tx) + require.NotNil(t, ids) + require.Len(t, ids, 2) + return nil + }, + cleanup: func() { + price = big.NewInt(0) + accountBalance = big.NewInt(0) + bp.chainCtx.NetworkParameters.DisabledGasCosts = true + }, + }, + { + name: "mark broadcasted for broadcasted voteIDs", + signer: validator, + events: []*types.VotableEvent{evt1, evt2}, + fn: func(ctx context.Context, bp *BlockProcessor, db sql.DB, es *mockEventStore) error { + tx, ids, err := bp.PrepareValidatorVoteIDTx(ctx, db) + require.NoError(t, err) + require.NotNil(t, tx) + require.NotNil(t, ids) + require.Len(t, ids, 2) + + err = bp.events.MarkBroadcasted(ctx, ids) + require.NoError(t, err) + + // now no more events to broadcast + tx, ids, err = bp.PrepareValidatorVoteIDTx(ctx, db) + require.NoError(t, err) + require.Nil(t, tx) + require.Nil(t, ids) + + // add more events + es.addEvent(evt3) + tx, ids, err = bp.PrepareValidatorVoteIDTx(ctx, db) + require.NoError(t, err) + require.NotNil(t, tx) + require.NotNil(t, ids) + require.Len(t, ids, 1) + + return nil + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if tc.cleanup != nil { + tc.cleanup() + } + }() + + db := &mockDB{} + bp.signer = tc.signer + es := newMockEventStore(tc.events) + bp.events = es + bp.validators = newValidatorStore(valSet) + + ctx := context.Background() + err := tc.fn(ctx, bp, db, es) // run the test function + require.NoError(t, err) + }) + } +} + +func TestPrepareVoteBodyTx(t *testing.T) { + signer := secp256k1Signer(t) + genCfg := config.DefaultGenesisConfig() + genCfg.Leader = signer.Identity() + + bp := &BlockProcessor{ + db: &mockDB{}, + log: log.DiscardLogger, + signer: signer, + chainCtx: &common.ChainContext{ + ChainID: "test", + NetworkParameters: &common.NetworkParameters{ + MaxBlockSize: 6 * 1024 * 1024, + MaxVotesPerTx: 100, + DisabledGasCosts: true, + }, + }, + txapp: &mockTxApp{}, + genesisParams: genCfg, + } + + testcases := []struct { + name string + events []*types.VotableEvent + cleanup func() + fn func(context.Context, *BlockProcessor, *mockEventStore) error + }{ + { + name: "No events to broadcast(gasless mode)", + events: []*types.VotableEvent{}, + fn: func(ctx context.Context, bp *BlockProcessor, es *mockEventStore) error { + tx, err := bp.prepareValidatorVoteBodyTx(ctx, 1, bp.chainCtx.NetworkParameters.MaxBlockSize) + require.NoError(t, err) + require.Nil(t, tx) + + return nil + }, + }, + { + name: "No events to broadcast (gas mode)", + events: []*types.VotableEvent{}, + cleanup: func() { + bp.chainCtx.NetworkParameters.DisabledGasCosts = true + }, + fn: func(ctx context.Context, bp *BlockProcessor, es *mockEventStore) error { + bp.chainCtx.NetworkParameters.DisabledGasCosts = false + + tx, err := bp.prepareValidatorVoteBodyTx(ctx, 1, bp.chainCtx.NetworkParameters.MaxBlockSize) + require.NoError(t, err) + require.Nil(t, tx) + + return nil + }, + }, + { + name: "atleast 1 event to broadcast", + events: []*types.VotableEvent{evt1, evt2}, + fn: func(ctx context.Context, bp *BlockProcessor, es *mockEventStore) error { + tx, err := bp.prepareValidatorVoteBodyTx(ctx, 1, bp.chainCtx.NetworkParameters.MaxBlockSize) + require.NoError(t, err) + require.NotNil(t, tx) + + txn := &types.Transaction{} + err = txn.UnmarshalBinary(tx) + require.NoError(t, err) + + var payload = &ktypes.ValidatorVoteBodies{} + err = payload.UnmarshalBinary(txn.Body.Payload) + require.NoError(t, err) + + require.Len(t, payload.Events, 2) + return nil + }, + }, + { + name: "enforce maxVotesPerTx limit", + events: []*types.VotableEvent{evt1, evt2, evt3}, + fn: func(ctx context.Context, bp *BlockProcessor, es *mockEventStore) error { + bp.chainCtx.NetworkParameters.MaxVotesPerTx = 1 + + tx, err := bp.prepareValidatorVoteBodyTx(ctx, 1, bp.chainCtx.NetworkParameters.MaxBlockSize) + require.NoError(t, err) + require.NotNil(t, tx) + + txn := &types.Transaction{} + err = txn.UnmarshalBinary(tx) + require.NoError(t, err) + + var payload = &ktypes.ValidatorVoteBodies{} + err = payload.UnmarshalBinary(txn.Body.Payload) + require.NoError(t, err) + + require.Len(t, payload.Events, 1) + return nil + }, + cleanup: func() { + bp.chainCtx.NetworkParameters.MaxVotesPerTx = 100 + }, + }, + { + name: "enforce maxSizePerTx limit", + events: []*types.VotableEvent{evt1, evt2, evt3}, + fn: func(ctx context.Context, bp *BlockProcessor, es *mockEventStore) error { + + emptyTxSize, err := bp.emptyVodeBodyTxSize() + require.NoError(t, err) + + // support evt1 + txSize := emptyTxSize + int64(len(evt1.Body)+len(evt1.Type)+8) + bp.chainCtx.NetworkParameters.MaxBlockSize = txSize + 10 /* buffer */ + + tx, err := bp.prepareValidatorVoteBodyTx(ctx, 1, bp.chainCtx.NetworkParameters.MaxBlockSize) + require.NoError(t, err) + require.NotNil(t, tx) + + txn := &types.Transaction{} + err = txn.UnmarshalBinary(tx) + require.NoError(t, err) + + var payload = &ktypes.ValidatorVoteBodies{} + err = payload.UnmarshalBinary(txn.Body.Payload) + require.NoError(t, err) + + require.Len(t, payload.Events, 1) + return nil + + }, + cleanup: func() { + bp.chainCtx.NetworkParameters.MaxBlockSize = 6 * 1024 * 1024 + }, + }, + { + name: "insufficient funds", + events: []*types.VotableEvent{evt1, evt2}, + fn: func(ctx context.Context, bp *BlockProcessor, es *mockEventStore) error { + bp.chainCtx.NetworkParameters.DisabledGasCosts = false + accountBalance = big.NewInt(0) + price = big.NewInt(1000) + + tx, err := bp.prepareValidatorVoteBodyTx(ctx, 1, bp.chainCtx.NetworkParameters.MaxBlockSize) + require.NoError(t, err) + require.Nil(t, tx) + + return nil + }, + cleanup: func() { + bp.chainCtx.NetworkParameters.DisabledGasCosts = true + }, + }, + { + name: "have sufficient funds", + events: []*types.VotableEvent{evt1, evt2}, + fn: func(ctx context.Context, bp *BlockProcessor, es *mockEventStore) error { + bp.chainCtx.NetworkParameters.DisabledGasCosts = false + accountBalance = big.NewInt(1000) + price = big.NewInt(1000) + + tx, err := bp.prepareValidatorVoteBodyTx(ctx, 1, bp.chainCtx.NetworkParameters.MaxBlockSize) + require.NoError(t, err) + require.NotNil(t, tx) + + txn := &types.Transaction{} + err = txn.UnmarshalBinary(tx) + require.NoError(t, err) + + var payload = &ktypes.ValidatorVoteBodies{} + err = payload.UnmarshalBinary(txn.Body.Payload) + require.NoError(t, err) + + require.Len(t, payload.Events, 2) + return nil + }, + cleanup: func() { + bp.chainCtx.NetworkParameters.DisabledGasCosts = true + accountBalance = big.NewInt(0) + price = big.NewInt(0) + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + // cleanup + defer func() { + if tc.cleanup != nil { + tc.cleanup() + } + }() + + es := newMockEventStore(tc.events) + bp.events = es + + getEvents = func(_ context.Context, _ sql.Executor) ([]*types.VotableEvent, error) { + return es.getEvents(), nil + } + + if tc.fn != nil { + tc.fn(context.Background(), bp, es) + } + }) + } + +} + +type mockTxApp struct{} + +var accountBalance = big.NewInt(0) + +func (m *mockTxApp) AccountInfo(ctx context.Context, db sql.DB, acctID []byte, getUncommitted bool) (balance *big.Int, nonce int64, err error) { + return accountBalance, 0, nil +} + +func (m *mockTxApp) ApplyMempool(ctx *common.TxContext, db sql.DB, tx *types.Transaction) error { + return nil +} + +func (m *mockTxApp) Begin(ctx context.Context, height int64) error { + return nil +} + +func (m *mockTxApp) Commit() error { + return nil +} + +func (m *mockTxApp) Rollback() {} + +func (m *mockTxApp) Execute(ctx *common.TxContext, db sql.DB, tx *types.Transaction) *txapp.TxResponse { + return nil +} + +func (m *mockTxApp) Finalize(ctx context.Context, db sql.DB, block *common.BlockContext) (validatorUpgrades []*types.Validator, err error) { + return nil, nil +} + +func (m *mockTxApp) GenesisInit(ctx context.Context, db sql.DB, validators []*types.Validator, accounts []*types.Account, + initialHeight int64, chain *common.ChainContext) error { + return nil +} + +func (m *mockTxApp) GetValidators(ctx context.Context, db sql.DB) ([]*types.Validator, error) { + return nil, nil +} + +func (m *mockTxApp) ProposerTxs(ctx context.Context, db sql.DB, txNonce uint64, maxTxSz int64, block *common.BlockContext) ([][]byte, error) { + return nil, nil +} + +func (m *mockTxApp) UpdateValidator(ctx context.Context, db sql.DB, validator []byte, power int64) error { + return nil +} + +func (m *mockTxApp) Reload(ctx context.Context, db sql.DB) error { + return nil +} + +var price = big.NewInt(0) + +func (m *mockTxApp) Price(ctx context.Context, db sql.DB, tx *types.Transaction, c *common.ChainContext) (*big.Int, error) { + return price, nil +} + +type mockDB struct{} + +func (m *mockDB) BeginPreparedTx(ctx context.Context) (sql.PreparedTx, error) { + return &mockTx{}, nil +} + +func (m *mockDB) BeginReadTx(ctx context.Context) (sql.OuterReadTx, error) { + return &mockTx{}, nil +} + +func (m *mockDB) BeginSnapshotTx(ctx context.Context) (sql.Tx, string, error) { + return &mockTx{}, "", nil +} + +func (m *mockDB) Execute(ctx context.Context, stmt string, args ...any) (*sql.ResultSet, error) { + return nil, nil +} + +func (m *mockDB) BeginTx(ctx context.Context) (sql.Tx, error) { + return &mockTx{}, nil +} + +func (m *mockDB) AutoCommit(on bool) {} + +type mockTx struct{} + +func (m *mockTx) Subscribe(ctx context.Context) (<-chan string, func(context.Context) error, error) { + return make(<-chan string), func(ctx context.Context) error { return nil }, nil +} + +func (m *mockTx) Rollback(ctx context.Context) error { + return nil +} + +func (m *mockTx) Commit(ctx context.Context) error { + return nil +} + +func (m *mockTx) Execute(ctx context.Context, stmt string, args ...any) (*sql.ResultSet, error) { + return nil, nil +} + +func (m *mockTx) BeginTx(ctx context.Context) (sql.Tx, error) { + return &mockTx{}, nil +} + +func (m *mockTx) Precommit(ctx context.Context, changes chan<- any) ([]byte, error) { + return nil, nil +} + +type event struct { + evt *types.VotableEvent + broadcasted bool +} + +type mockEventStore struct { + events map[string]event +} + +func newMockEventStore(events []*types.VotableEvent) *mockEventStore { + es := &mockEventStore{events: make(map[string]event)} + for _, e := range events { + es.events[e.ID().String()] = event{evt: e, broadcasted: false} + } + return es +} + +func (m *mockEventStore) addEvent(evt *types.VotableEvent) { + m.events[evt.ID().String()] = event{evt: evt, broadcasted: false} +} + +func (m *mockEventStore) getEvents() []*types.VotableEvent { + var events []*types.VotableEvent + for _, e := range m.events { + events = append(events, e.evt) + } + return events +} +func (m *mockEventStore) GetUnbroadcastedEvents(ctx context.Context) ([]*types.UUID, error) { + var ids []*types.UUID + for _, e := range m.events { + if !e.broadcasted { + ids = append(ids, e.evt.ID()) + } + } + return ids, nil +} + +func (m *mockEventStore) MarkBroadcasted(ctx context.Context, ids []*types.UUID) error { + for _, id := range ids { + if e, ok := m.events[id.String()]; ok { + e.broadcasted = true + m.events[id.String()] = e + } + } + return nil +} + +type mockValidatorStore struct { + valSet []*ktypes.Validator +} + +func newValidatorStore(valSet []*ktypes.Validator) *mockValidatorStore { + return &mockValidatorStore{ + valSet: valSet, + } +} + +func (v *mockValidatorStore) GetValidators() []*ktypes.Validator { + return v.valSet +} + +func (v *mockValidatorStore) ValidatorUpdates() map[string]*ktypes.Validator { + return nil +} diff --git a/node/consensus/block.go b/node/consensus/block.go index 76a3ae69f..f4dbbcb8c 100644 --- a/node/consensus/block.go +++ b/node/consensus/block.go @@ -102,7 +102,7 @@ func (ce *ConsensusEngine) commit(ctx context.Context) error { Height: height, AppHash: appHash, // To indicate if the node is syncing, used by the blockprocessor to decide if it should create snapshots. - Syncing: ce.inSync.Load(), + Syncing: ce.InCatchup(), } if err := ce.blockProcessor.Commit(ctx, req); err != nil { // clears the mempool cache return err diff --git a/node/consensus/blocksync.go b/node/consensus/blocksync.go index 938aa8525..81b5445da 100644 --- a/node/consensus/blocksync.go +++ b/node/consensus/blocksync.go @@ -62,7 +62,7 @@ func (ce *ConsensusEngine) discoverBestHeight(ctx context.Context) (int64, error case <-cancelCtx.Done(): return case <-time.After(delay): - if ce.inSync.Load() { + if ce.InCatchup() { // if we are still in catchup, broadcast again ce.discoveryReqBroadcaster() delay = min(2*delay, 16*time.Second) } diff --git a/node/consensus/engine.go b/node/consensus/engine.go index 197ee4f04..6b94cd25d 100644 --- a/node/consensus/engine.go +++ b/node/consensus/engine.go @@ -15,6 +15,7 @@ import ( "github.com/kwilteam/kwil-db/core/crypto" "github.com/kwilteam/kwil-db/core/log" ktypes "github.com/kwilteam/kwil-db/core/types" + blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" "github.com/kwilteam/kwil-db/node/meta" "github.com/kwilteam/kwil-db/node/pg" "github.com/kwilteam/kwil-db/node/types" @@ -259,7 +260,7 @@ func New(cfg *Config) *ConsensusEngine { func (ce *ConsensusEngine) Start(ctx context.Context, proposerBroadcaster ProposalBroadcaster, blkAnnouncer BlkAnnouncer, ackBroadcaster AckBroadcaster, blkRequester BlkRequester, stateResetter ResetStateBroadcaster, - discoveryReqBroadcaster DiscoveryReqBroadcaster) error { + discoveryReqBroadcaster DiscoveryReqBroadcaster, txBroadcaster blockprocessor.BroadcastTxFn) error { ce.proposalBroadcaster = proposerBroadcaster ce.blkAnnouncer = blkAnnouncer ce.ackBroadcaster = ackBroadcaster @@ -267,6 +268,8 @@ func (ce *ConsensusEngine) Start(ctx context.Context, proposerBroadcaster Propos ce.rstStateBroadcaster = stateResetter ce.discoveryReqBroadcaster = discoveryReqBroadcaster + ce.blockProcessor.SetBroadcastTxFn(txBroadcaster) + ce.log.Info("Starting the consensus engine") ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -716,3 +719,7 @@ func (ce *ConsensusEngine) hasMajorityFloor(cnt int) bool { threshold := len(ce.validatorSet) / 2 return cnt >= threshold } + +func (ce *ConsensusEngine) InCatchup() bool { + return ce.inSync.Load() +} diff --git a/node/consensus/engine_test.go b/node/consensus/engine_test.go index 2499bfee2..315d3cbf7 100644 --- a/node/consensus/engine_test.go +++ b/node/consensus/engine_test.go @@ -19,6 +19,7 @@ import ( "github.com/kwilteam/kwil-db/common" "github.com/kwilteam/kwil-db/config" "github.com/kwilteam/kwil-db/core/crypto" + "github.com/kwilteam/kwil-db/core/crypto/auth" ktypes "github.com/kwilteam/kwil-db/core/types" blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" "github.com/kwilteam/kwil-db/node/mempool" @@ -114,8 +115,19 @@ func generateTestCEConfig(t *testing.T, nodes int, leaderDB bool) []*Config { bs, err := store.NewBlockStore(nodeDir) assert.NoError(t, err) - bp, err := blockprocessor.NewBlockProcessor(ctx, db, txapp, accounts, v, ss, genCfg, log.New(log.WithName("BP"))) + signer := auth.GetNodeSigner(privKeys[i]) + + ev := &mockEventStore{} + + bp, err := blockprocessor.NewBlockProcessor(ctx, db, txapp, accounts, v, ss, ev, genCfg, signer, log.New(log.WithName("BP"))) assert.NoError(t, err) + bp.SetNetworkParameters(&common.NetworkParameters{ + MaxBlockSize: genCfg.MaxBlockSize, + JoinExpiry: genCfg.JoinExpiry, + VoteExpiry: genCfg.VoteExpiry, + DisabledGasCosts: true, + MaxVotesPerTx: genCfg.MaxVotesPerTx, + }) ceConfigs[i] = &Config{ PrivateKey: privKeys[i], @@ -551,19 +563,21 @@ func TestValidatorStateMachine(t *testing.T) { leader := New(ceConfigs[0]) val := New(ceConfigs[1]) - blkProp1, err = leader.createBlockProposal() + + ctxM := context.Background() + blkProp1, err = leader.createBlockProposal(ctxM) assert.NoError(t, err) time.Sleep(300 * time.Millisecond) // just to ensure that the block hashes are different due to start time - blkProp2, err = leader.createBlockProposal() + blkProp2, err = leader.createBlockProposal(ctxM) assert.NoError(t, err) t.Logf("blkProp1: %s, blkProp2: %s", blkProp1.blkHash.String(), blkProp2.blkHash.String()) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctxM) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() - val.Start(ctx, mockBlockPropBroadcaster, mockBlkAnnouncer, mockVoteBroadcaster, mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster) + val.Start(ctx, mockBlockPropBroadcaster, mockBlkAnnouncer, mockVoteBroadcaster, mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster, nil) }() t.Cleanup(func() { @@ -599,7 +613,7 @@ func TestCELeaderSingleNode(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - leader.Start(ctx, mockBlockPropBroadcaster, mockBlkAnnouncer, mockVoteBroadcaster, mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster) + leader.Start(ctx, mockBlockPropBroadcaster, mockBlkAnnouncer, mockVoteBroadcaster, mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster, nil) }() t.Cleanup(func() { @@ -625,7 +639,7 @@ func TestCELeaderTwoNodesMajorityAcks(t *testing.T) { go func() { defer wg.Done() n1.Start(ctx, mockBlockPropBroadcaster, mockBlkAnnouncer, mockVoteBroadcaster, - mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster) + mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster, nil) }() t.Cleanup(func() { @@ -686,7 +700,7 @@ func TestCELeaderTwoNodesMajorityNacks(t *testing.T) { go func() { defer wg.Done() - n1.Start(ctx, mockBlockPropBroadcaster, mockBlkAnnouncer, mockVoteBroadcaster, mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster) + n1.Start(ctx, mockBlockPropBroadcaster, mockBlkAnnouncer, mockVoteBroadcaster, mockBlockRequester, mockResetStateBroadcaster, mockDiscoveryBroadcaster, nil) }() n1.bestHeightCh <- &discoveryMsg{ @@ -863,3 +877,19 @@ func (s *snapshotStore) IsSnapshotDue(height uint64) bool { func (s *snapshotStore) LoadSnapshotChunk(height uint64, format uint32, chunkID uint32) ([]byte, error) { return nil, nil } + +type mockEventStore struct { + events []*ktypes.VotableEvent +} + +func (m *mockEventStore) MarkBroadcasted(ctx context.Context, ids []*ktypes.UUID) error { + return nil +} + +func (m *mockEventStore) GetUnbroadcastedEvents(ctx context.Context) ([]*ktypes.UUID, error) { + var ids []*ktypes.UUID + for _, event := range m.events { + ids = append(ids, event.ID()) + } + return ids, nil +} diff --git a/node/consensus/interfaces.go b/node/consensus/interfaces.go index 16400405c..d13e47e48 100644 --- a/node/consensus/interfaces.go +++ b/node/consensus/interfaces.go @@ -3,6 +3,7 @@ package consensus import ( "context" + blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" "github.com/kwilteam/kwil-db/node/mempool" ktypes "github.com/kwilteam/kwil-db/core/types" @@ -40,6 +41,9 @@ type BlockStore interface { type BlockProcessor interface { InitChain(ctx context.Context) (int64, []byte, error) + SetBroadcastTxFn(fn blockprocessor.BroadcastTxFn) + + PrepareProposal(ctx context.Context, txs [][]byte) (finalTxs [][]byte, invalidTxs [][]byte, err error) ExecuteBlock(ctx context.Context, req *ktypes.BlockExecRequest) (*ktypes.BlockExecResult, error) Commit(ctx context.Context, req *ktypes.CommitRequest) error Rollback(ctx context.Context, height int64, appHash ktypes.Hash) error diff --git a/node/consensus/leader.go b/node/consensus/leader.go index 95b09b4ea..afffa503e 100644 --- a/node/consensus/leader.go +++ b/node/consensus/leader.go @@ -38,7 +38,7 @@ func (ce *ConsensusEngine) startNewRound(ctx context.Context) error { ce.log.Info("Starting a new consensus round", "height", ce.state.lc.height+1) - blkProp, err := ce.createBlockProposal() + blkProp, err := ce.createBlockProposal(ctx) if err != nil { ce.log.Errorf("Error creating a block proposal: %v", err) return err @@ -127,7 +127,7 @@ func (ce *ConsensusEngine) startNewRound(ctx context.Context) error { // proposer transactions such as ValidatorVoteBodies. // This method orders the transactions in the nonce order and also // does basic gas and balance checks and enforces the block size limits. -func (ce *ConsensusEngine) createBlockProposal() (*blockProposal, error) { +func (ce *ConsensusEngine) createBlockProposal(ctx context.Context) (*blockProposal, error) { nTxs := ce.mempool.PeekN(blockTxCount) var txns [][]byte for _, namedTx := range nTxs { @@ -142,7 +142,18 @@ func (ce *ConsensusEngine) createBlockProposal() (*blockProposal, error) { txns = append(txns, rawTx) } - blk := ktypes.NewBlock(ce.state.lc.height+1, ce.state.lc.blkHash, ce.state.lc.appHash, ce.ValidatorSetHash(), time.Now(), txns) + finalTxs, invalidTxs, err := ce.blockProcessor.PrepareProposal(ctx, txns) + if err != nil { + ce.log.Errorf("Error preparing the block proposal: %v", err) + return nil, err + } + + // remove invalid transactions from the mempool + for _, tx := range invalidTxs { + ce.mempool.Remove(types.Hash(tx)) + } + + blk := ktypes.NewBlock(ce.state.lc.height+1, ce.state.lc.blkHash, ce.state.lc.appHash, ce.ValidatorSetHash(), time.Now(), finalTxs) // ValSet + valUpdatesHash diff --git a/node/interfaces.go b/node/interfaces.go index a575be342..94107f827 100644 --- a/node/interfaces.go +++ b/node/interfaces.go @@ -4,6 +4,7 @@ import ( "context" ktypes "github.com/kwilteam/kwil-db/core/types" + blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" "github.com/kwilteam/kwil-db/node/consensus" "github.com/kwilteam/kwil-db/node/snapshotter" "github.com/kwilteam/kwil-db/node/types" @@ -12,6 +13,7 @@ import ( type ConsensusEngine interface { Role() types.Role // maybe: Role() (rol types.Role, power int64) + InCatchup() bool AcceptProposal(height int64, blkID, prevBlkID types.Hash, leaderSig []byte, timestamp int64) bool NotifyBlockProposal(blk *ktypes.Block) @@ -27,7 +29,7 @@ type ConsensusEngine interface { Start(ctx context.Context, proposerBroadcaster consensus.ProposalBroadcaster, blkAnnouncer consensus.BlkAnnouncer, ackBroadcaster consensus.AckBroadcaster, - blkRequester consensus.BlkRequester, stateResetter consensus.ResetStateBroadcaster, discoveryBroadcaster consensus.DiscoveryReqBroadcaster) error + blkRequester consensus.BlkRequester, stateResetter consensus.ResetStateBroadcaster, discoveryBroadcaster consensus.DiscoveryReqBroadcaster, txBroadcaster blockprocessor.BroadcastTxFn) error CheckTx(ctx context.Context, tx *ktypes.Transaction) error diff --git a/node/_listeners/mgr.go b/node/listeners/mgr.go similarity index 88% rename from node/_listeners/mgr.go rename to node/listeners/mgr.go index b6e9f05ee..3f970342f 100644 --- a/node/_listeners/mgr.go +++ b/node/listeners/mgr.go @@ -8,11 +8,9 @@ import ( "time" common "github.com/kwilteam/kwil-db/common" - "github.com/kwilteam/kwil-db/core/log" "github.com/kwilteam/kwil-db/core/types" "github.com/kwilteam/kwil-db/extensions/listeners" - "github.com/kwilteam/kwil-db/internal/abci/cometbft" - "github.com/kwilteam/kwil-db/internal/voting" + "github.com/kwilteam/kwil-db/node/voting" ) // ListenerManager listens for any Validator state changes and node catch up status @@ -22,23 +20,26 @@ import ( type ListenerManager struct { eventStore *voting.EventStore vstore ValidatorGetter - cometNode *cometbft.CometBftNode + node Node service *common.Service cancel context.CancelFunc // cancels the context for the listener manager } // ValidatorGetter is able to read the current validator set. type ValidatorGetter interface { - GetValidators(ctx context.Context) ([]*types.Validator, error) + GetValidators() []*types.Validator SubscribeValidators() <-chan []*types.Validator } -func NewListenerManager(svc *common.Service, eventStore *voting.EventStore, - node *cometbft.CometBftNode, vstore ValidatorGetter) *ListenerManager { +type Node interface { + InCatchup() bool +} + +func NewListenerManager(svc *common.Service, eventStore *voting.EventStore, vstore ValidatorGetter, node Node) *ListenerManager { return &ListenerManager{ eventStore: eventStore, vstore: vstore, - cometNode: node, + node: node, service: svc, } } @@ -77,14 +78,14 @@ func (omgr *ListenerManager) Start() (err error) { }) if err != nil { omgr.service.Logger.Error("========================== Event listener stopped ==========================", - log.String("listener", name), log.Error(err)) + "listener", name, "error", err) if !errors.Is(err, context.Canceled) { errChan <- err } cancel2() // Stop other listeners } else { // Listener exited with nil, no need to stop other listeners in this case - omgr.service.Logger.Debug("Event listener stopped (cleanly)", log.String("listener", name)) + omgr.service.Logger.Debug("Event listener stopped (cleanly)", "listener", name) } }(start, name) @@ -98,7 +99,7 @@ func (omgr *ListenerManager) Start() (err error) { } defer func() { - omgr.service.Logger.Info("ListenerManager stopped.", log.Error(err)) + omgr.service.Logger.Info("ListenerManager stopped.", "error", err) }() containsMe := func(validators []*types.Validator) bool { @@ -127,7 +128,7 @@ func (omgr *ListenerManager) Start() (err error) { case <-syncCheck.C: // still in catch up mode, keep polling - if omgr.cometNode.IsCatchup() { + if omgr.node.InCatchup() { continue } @@ -135,10 +136,7 @@ func (omgr *ListenerManager) Start() (err error) { syncCheck.Stop() valChan = omgr.vstore.SubscribeValidators() // creates a new channel in txApp - validators, err := omgr.vstore.GetValidators(ctx) - if err != nil { - return err - } + validators := omgr.vstore.GetValidators() startStop(containsMe(validators)) } } diff --git a/node/node.go b/node/node.go index 70ea19e85..4bf88d8f0 100644 --- a/node/node.go +++ b/node/node.go @@ -359,7 +359,7 @@ func (n *Node) Start(ctx context.Context, bootpeers ...string) error { defer n.wg.Done() defer cancel() // TODO: umm, should node bringup the consensus engine? or server? - nodeErr = n.ce.Start(ctx, n.announceBlkProp, n.announceBlk, n.sendACK, n.getBlkHeight, n.sendReset, n.sendDiscoveryRequest) + nodeErr = n.ce.Start(ctx, n.announceBlkProp, n.announceBlk, n.sendACK, n.getBlkHeight, n.sendReset, n.sendDiscoveryRequest, n.BroadcastTx) if err != nil { n.log.Errorf("Consensus engine failed: %v", nodeErr) return // cancel context @@ -475,10 +475,9 @@ func (n *Node) Status(ctx context.Context) (*adminTypes.Status, error) { BestBlockHash: blkHash[:], BestBlockHeight: height, // BestBlockTime: , - Syncing: false, // n.ce.Status().Syncing ??? need a node/exec pkg for block_executor stuff? + Syncing: n.ce.InCatchup(), }, Validator: &adminTypes.ValidatorInfo{ - Role: n.ce.Role().String(), PubKey: pkBytes, // Power: 1, }, @@ -729,3 +728,7 @@ func ExpandPath(path string) (string, error) { } return filepath.Abs(path) } + +func (n *Node) InCatchup() bool { + return n.ce.InCatchup() +} diff --git a/node/node_live_test.go b/node/node_live_test.go index 08eabdb63..aeef7a7d5 100644 --- a/node/node_live_test.go +++ b/node/node_live_test.go @@ -17,6 +17,8 @@ import ( "github.com/kwilteam/kwil-db/common" "github.com/kwilteam/kwil-db/config" + "github.com/kwilteam/kwil-db/core/crypto" + "github.com/kwilteam/kwil-db/core/crypto/auth" "github.com/kwilteam/kwil-db/core/log" ktypes "github.com/kwilteam/kwil-db/core/types" blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" @@ -84,8 +86,15 @@ func TestSingleNodeMocknet(t *testing.T) { genCfg.Leader = privKeys[0].Public().Bytes() genCfg.Validators = valSetList + k, err := crypto.UnmarshalSecp256k1PrivateKey(pk1) + require.NoError(t, err) + + signer1 := &auth.EthPersonalSigner{Key: *k} + + es := &mockEventStore{} + bpl := log.New(log.WithName("BP1"), log.WithWriter(os.Stdout), log.WithLevel(log.LevelDebug), log.WithFormat(log.FormatUnstructured)) - bp, err := blockprocessor.NewBlockProcessor(ctx, db1, newDummyTxApp(valSetList), &mockAccounts{}, vsReal, ss, genCfg, bpl) + bp, err := blockprocessor.NewBlockProcessor(ctx, db1, newDummyTxApp(valSetList), &mockAccounts{}, vsReal, ss, es, genCfg, signer1, bpl) require.NoError(t, err) ceCfg1 := &consensus.Config{ @@ -203,8 +212,14 @@ func TestDualNodeMocknet(t *testing.T) { // _, vsReal, err := voting.NewResolutionStore(ctx, db1) + k, err := crypto.UnmarshalSecp256k1PrivateKey(pk1) + require.NoError(t, err) + + signer1 := &auth.EthPersonalSigner{Key: *k} + es1 := &mockEventStore{} + bpl1 := log.New(log.WithName("BP1"), log.WithWriter(os.Stdout), log.WithLevel(log.LevelDebug), log.WithFormat(log.FormatUnstructured)) - bp1, err := blockprocessor.NewBlockProcessor(ctx, db1, newDummyTxApp(valSetList), &mockAccounts{}, newValidatorStore(valSetList), ss, genCfg, bpl1) + bp1, err := blockprocessor.NewBlockProcessor(ctx, db1, newDummyTxApp(valSetList), &mockAccounts{}, newValidatorStore(valSetList), ss, es1, genCfg, signer1, bpl1) ceCfg1 := &consensus.Config{ PrivateKey: privKeys[0], @@ -246,9 +261,14 @@ func TestDualNodeMocknet(t *testing.T) { time.Sleep(20 * time.Millisecond) + k2, err := crypto.UnmarshalSecp256k1PrivateKey(pk2) + require.NoError(t, err) + + signer2 := &auth.EthPersonalSigner{Key: *k2} + es2 := &mockEventStore{} // _, vsReal2, err := voting.NewResolutionStore(ctx, db2) bpl2 := log.New(log.WithName("BP2"), log.WithWriter(os.Stdout), log.WithLevel(log.LevelDebug), log.WithFormat(log.FormatUnstructured)) - bp2, err := blockprocessor.NewBlockProcessor(ctx, db2, newDummyTxApp(valSetList), &mockAccounts{}, newValidatorStore(valSetList), ss, genCfg, bpl2) + bp2, err := blockprocessor.NewBlockProcessor(ctx, db2, newDummyTxApp(valSetList), &mockAccounts{}, newValidatorStore(valSetList), ss, es2, genCfg, signer2, bpl2) ceCfg2 := &consensus.Config{ PrivateKey: privKeys[1], ValidatorSet: valSet, @@ -402,3 +422,19 @@ type mockAccounts struct{} func (m *mockAccounts) Updates() []*ktypes.Account { return nil } + +type mockEventStore struct { + events []*ktypes.VotableEvent +} + +func (m *mockEventStore) MarkBroadcasted(ctx context.Context, ids []*ktypes.UUID) error { + return nil +} + +func (m *mockEventStore) GetUnbroadcastedEvents(ctx context.Context) ([]*ktypes.UUID, error) { + var ids []*ktypes.UUID + for _, event := range m.events { + ids = append(ids, event.ID()) + } + return ids, nil +} diff --git a/node/node_test.go b/node/node_test.go index 8f67e227d..7eb793e04 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -20,6 +20,7 @@ import ( "github.com/kwilteam/kwil-db/core/crypto" "github.com/kwilteam/kwil-db/core/log" ktypes "github.com/kwilteam/kwil-db/core/types" + blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" "github.com/kwilteam/kwil-db/node/consensus" "github.com/kwilteam/kwil-db/node/mempool" "github.com/kwilteam/kwil-db/node/store/memstore" @@ -196,9 +197,14 @@ func (ce *dummyCE) ConsensusParams() *ktypes.ConsensusParams { return nil } +func (ce *dummyCE) InCatchup() bool { + return false +} + func (ce *dummyCE) Start(ctx context.Context, proposerBroadcaster consensus.ProposalBroadcaster, blkAnnouncer consensus.BlkAnnouncer, ackBroadcaster consensus.AckBroadcaster, - blkRequester consensus.BlkRequester, stateResetter consensus.ResetStateBroadcaster, discReqBroadcaster consensus.DiscoveryReqBroadcaster) error { + blkRequester consensus.BlkRequester, stateResetter consensus.ResetStateBroadcaster, + discReqBroadcaster consensus.DiscoveryReqBroadcaster, txBroadcaster blockprocessor.BroadcastTxFn) error { ce.proposerBroadcaster = proposerBroadcaster ce.blkAnnouncer = blkAnnouncer ce.ackBroadcaster = ackBroadcaster diff --git a/node/services/jsonrpc/adminsvc/service.go b/node/services/jsonrpc/adminsvc/service.go index f94eebb57..d9aa29214 100644 --- a/node/services/jsonrpc/adminsvc/service.go +++ b/node/services/jsonrpc/adminsvc/service.go @@ -16,7 +16,6 @@ import ( types "github.com/kwilteam/kwil-db/core/types/admin" "github.com/kwilteam/kwil-db/extensions/resolutions" rpcserver "github.com/kwilteam/kwil-db/node/services/jsonrpc" - nodetypes "github.com/kwilteam/kwil-db/node/types" "github.com/kwilteam/kwil-db/node/types/sql" "github.com/kwilteam/kwil-db/node/voting" "github.com/kwilteam/kwil-db/version" @@ -141,7 +140,6 @@ func (svc *Service) HealthMethod(ctx context.Context, _ *userjson.HealthRequest) Healthy: happy, Version: apiSemver, PubKey: status.Validator.PubKey, - Role: status.Validator.Role, NumValidators: len(vals.Validators), }, nil // slices.ContainsFunc(vals.Validators, func(v *ktypes.Validator) bool { return bytes.Equal(v.PubKey, status.Validator.PubKey) }) @@ -254,16 +252,12 @@ func (svc *Service) Status(ctx context.Context, req *adminjson.StatusRequest) (* } var power int64 - switch status.Validator.Role { - case nodetypes.RoleLeader.String(), nodetypes.RoleValidator.String(): - power, _ = svc.voting.GetValidatorPower(ctx, status.Validator.PubKey) - } + power, _ = svc.voting.GetValidatorPower(ctx, status.Validator.PubKey) return &adminjson.StatusResponse{ Node: status.Node, Sync: convertSyncInfo(status.Sync), Validator: &adminjson.Validator{ // TODO: weed out the type dups - Role: status.Validator.Role, PubKey: status.Validator.PubKey, Power: power, }, @@ -401,7 +395,6 @@ func (svc *Service) ListValidators(ctx context.Context, req *adminjson.ListValid pbValidators := make([]*adminjson.Validator, len(vals)) for i, vi := range vals { pbValidators[i] = &adminjson.Validator{ - Role: vi.Role, PubKey: vi.PubKey, Power: vi.Power, } diff --git a/node/txapp/routes_test.go b/node/txapp/routes_test.go index dab0bd45a..9e7de5233 100644 --- a/node/txapp/routes_test.go +++ b/node/txapp/routes_test.go @@ -353,15 +353,10 @@ func (v *mockValidator) Commit() error { func (v *mockValidator) Rollback() {} func getSigner(hexPrivKey string) auth.Signer { - //pk, _, err := crypto.GenerateSecp256k1Key(nil) bts, err := hex.DecodeString(hexPrivKey) if err != nil { panic(err) } - // pk, err := crypto.UnmarshalEd25519PrivateKey(bts) - // if err != nil { - // panic(err) - // } pk, err := crypto.UnmarshalSecp256k1PrivateKey(bts) if err != nil { panic(err) diff --git a/node/txapp/txapp.go b/node/txapp/txapp.go index d47dc6421..4b83b1459 100644 --- a/node/txapp/txapp.go +++ b/node/txapp/txapp.go @@ -64,12 +64,11 @@ func NewTxApp(ctx context.Context, db sql.Executor, engine common.Engine, signer events: events, mempool: &mempool{ - accounts: make(map[string]*types.Account), - // TODO: what is this nodeAddr used for? - // nodeAddr: signer.Identity(), + accounts: make(map[string]*types.Account), accountMgr: accounts, validatorMgr: validators, - log: service.Logger, + nodeAddr: signer.Identity(), + log: service.Logger.New("mempool"), }, signer: signer, resTypes: resTypes, @@ -123,7 +122,7 @@ func (r *TxApp) GenesisInit(ctx context.Context, db sql.DB, validators []*types. // use them to store any changes to the network parameters in the database during Finalize. func (r *TxApp) Begin(ctx context.Context, height int64) error { // Before executing transaction in this block, add/remove/update functionality. - // TODO: + // TODO: active forks, not for the beta return nil } @@ -602,6 +601,10 @@ func (r *TxApp) announceValidators() { } } +func (r *TxApp) GetValidators() []*types.Validator { + return r.Validators.GetValidators() +} + func validatorSetPower(validators []*types.Validator) int64 { var totalPower int64 for _, v := range validators { diff --git a/node/voting/voting.go b/node/voting/voting.go index 9e3878de4..bf319349e 100644 --- a/node/voting/voting.go +++ b/node/voting/voting.go @@ -603,7 +603,10 @@ func (v *VoteStore) GetValidators() []*types.Validator { vals := make([]*types.Validator, 0) for _, val := range v.validatorSet { - vals = append(vals, val) + vals = append(vals, &types.Validator{ + PubKey: slices.Clone(val.PubKey), + Power: val.Power, + }) } return vals