From 9b0b88b0389cc28f4dd7be80f7bf9b6ae45f1b2e Mon Sep 17 00:00:00 2001 From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com> Date: Mon, 30 Oct 2023 12:55:00 +0100 Subject: [PATCH] Adds pebble db store backend (#80) --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 11 ++ eth/backend.go | 5 + suave/core/config.go | 1 + suave/cstore/pebble_store_backend.go | 176 +++++++++++++++++++++++++++ suave/cstore/pebble_test.go | 11 ++ 6 files changed, 205 insertions(+) create mode 100644 suave/cstore/pebble_store_backend.go create mode 100644 suave/cstore/pebble_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e5d1bc992..caf35eff5 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -192,6 +192,7 @@ var ( utils.SuaveEthRemoteBackendEndpointFlag, utils.SuaveConfidentialTransportRedisEndpointFlag, utils.SuaveConfidentialStoreRedisEndpointFlag, + utils.SuaveConfidentialStorePebbleDbPathFlag, utils.SuaveEthBundleSigningKeyFlag, utils.SuaveEthBlockSigningKeyFlag, utils.SuaveDevModeFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9b6921cb0..7180e51a5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -537,6 +537,12 @@ var ( Category: flags.SuaveCategory, } + SuaveConfidentialStorePebbleDbPathFlag = &cli.StringFlag{ + Name: "suave.confidential.pebble-store-db-path", + Usage: "Path to pebble db to use for confidential storage backend (default: local store)", + Category: flags.SuaveCategory, + } + SuaveEthBundleSigningKeyFlag = &cli.StringFlag{ Name: "suave.eth.bundle-signing-key", EnvVars: []string{"SUAVE_ETH_BUNDLE_SIGNING_KEY"}, @@ -1700,6 +1706,7 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { } func SetSuaveConfig(ctx *cli.Context, stack *node.Node, cfg *suave.Config) { + CheckExclusive(ctx, SuaveConfidentialStoreRedisEndpointFlag, SuaveConfidentialStorePebbleDbPathFlag) if ctx.IsSet(SuaveEthRemoteBackendEndpointFlag.Name) { cfg.SuaveEthRemoteBackendEndpoint = ctx.String(SuaveEthRemoteBackendEndpointFlag.Name) } @@ -1712,6 +1719,10 @@ func SetSuaveConfig(ctx *cli.Context, stack *node.Node, cfg *suave.Config) { cfg.RedisStoreUri = ctx.String(SuaveConfidentialStoreRedisEndpointFlag.Name) } + if ctx.IsSet(SuaveConfidentialStorePebbleDbPathFlag.Name) { + cfg.PebbleDbPath = ctx.String(SuaveConfidentialStorePebbleDbPathFlag.Name) + } + if ctx.IsSet(SuaveEthBundleSigningKeyFlag.Name) { cfg.EthBundleSigningKeyHex = ctx.String(SuaveEthBundleSigningKeyFlag.Name) } diff --git a/eth/backend.go b/eth/backend.go index 56b8e08b4..06601a29d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -240,6 +240,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + } else if config.Suave.PebbleDbPath != "" { + confidentialStoreBackend, err = cstore.NewPebbleStoreBackend(config.Suave.PebbleDbPath) + if err != nil { + return nil, err + } } else { confidentialStoreBackend = cstore.NewLocalConfidentialStore() } diff --git a/suave/core/config.go b/suave/core/config.go index 9dad3f184..0e75897ac 100644 --- a/suave/core/config.go +++ b/suave/core/config.go @@ -4,6 +4,7 @@ type Config struct { SuaveEthRemoteBackendEndpoint string RedisStorePubsubUri string RedisStoreUri string + PebbleDbPath string EthBundleSigningKeyHex string EthBlockSigningKeyHex string } diff --git a/suave/cstore/pebble_store_backend.go b/suave/cstore/pebble_store_backend.go new file mode 100644 index 000000000..6cadd3369 --- /dev/null +++ b/suave/cstore/pebble_store_backend.go @@ -0,0 +1,176 @@ +package cstore + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/cockroachdb/pebble" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + suave "github.com/ethereum/go-ethereum/suave/core" +) + +var ( + formatPebbleBidKey = formatRedisBidKey + formatPebbleBidValueKey = formatRedisBidValueKey +) + +type PebbleStoreBackend struct { + ctx context.Context + cancel context.CancelFunc + dbPath string + db *pebble.DB +} + +var bidByBlockAndProtocolIndexDbKey = func(blockNumber uint64, namespace string) []byte { + return []byte(fmt.Sprintf("bids-block-%d-ns-%s", blockNumber, namespace)) +} + +type bidByBlockAndProtocolIndexType = []types.BidId + +func NewPebbleStoreBackend(dbPath string) (*PebbleStoreBackend, error) { + // TODO: should we check sanity in the constructor? + backend := &PebbleStoreBackend{ + dbPath: dbPath, + } + + return backend, backend.start() +} + +func (b *PebbleStoreBackend) start() error { + if b.cancel != nil { + b.cancel() + } + + ctx, cancel := context.WithCancel(context.Background()) + b.cancel = cancel + b.ctx = ctx + + db, err := pebble.Open(b.dbPath, &pebble.Options{}) + if err != nil { + return fmt.Errorf("could not open pebble database at %s: %w", b.dbPath, err) + } + + go func() { + <-ctx.Done() + db.Close() + }() + + b.db = db + + return nil +} + +func (b *PebbleStoreBackend) Stop() error { + b.cancel() + return nil +} + +func (b *PebbleStoreBackend) InitializeBid(bid suave.Bid) error { + key := []byte(formatPebbleBidKey(bid.Id)) + + _, closer, err := b.db.Get(key) + if !errors.Is(err, pebble.ErrNotFound) { + if err == nil { + closer.Close() + } + return suave.ErrBidAlreadyPresent + } + + data, err := json.Marshal(bid) + if err != nil { + return err + } + + err = b.db.Set(key, data, nil) + if err != nil { + return err + } + + // index update + var currentValues bidByBlockAndProtocolIndexType + + dbBlockProtoIndexKey := bidByBlockAndProtocolIndexDbKey(bid.DecryptionCondition, bid.Version) + rawCurrentValues, closer, err := b.db.Get(dbBlockProtoIndexKey) + if err != nil { + if !errors.Is(err, pebble.ErrNotFound) { + return err + } + } else if err == nil { + err = json.Unmarshal(rawCurrentValues, ¤tValues) + closer.Close() + if err != nil { + return err + } + } + + currentValues = append(currentValues, bid.Id) + rawUpdatedValues, err := json.Marshal(currentValues) + if err != nil { + return err + } + + return b.db.Set(dbBlockProtoIndexKey, rawUpdatedValues, nil) +} + +func (b *PebbleStoreBackend) FetchBidById(bidId suave.BidId) (suave.Bid, error) { + key := []byte(formatPebbleBidKey(bidId)) + + bidData, closer, err := b.db.Get(key) + if err != nil { + return suave.Bid{}, fmt.Errorf("bid %x not found: %w", bidId, err) + } + + var bid suave.Bid + err = json.Unmarshal(bidData, &bid) + closer.Close() + if err != nil { + return suave.Bid{}, fmt.Errorf("could not unmarshal stored bid: %w", err) + } + + return bid, nil +} + +func (b *PebbleStoreBackend) Store(bid suave.Bid, caller common.Address, key string, value []byte) (suave.Bid, error) { + storeKey := []byte(formatPebbleBidValueKey(bid.Id, key)) + return bid, b.db.Set(storeKey, value, nil) +} + +func (b *PebbleStoreBackend) Retrieve(bid suave.Bid, caller common.Address, key string) ([]byte, error) { + storeKey := []byte(formatPebbleBidValueKey(bid.Id, key)) + data, closer, err := b.db.Get(storeKey) + if err != nil { + return nil, fmt.Errorf("could not fetch data for bid %x and key %s: %w", bid.Id, key, err) + } + ret := make([]byte, len(data)) + copy(ret, data) + closer.Close() + return ret, nil +} + +func (b *PebbleStoreBackend) FetchBidsByProtocolAndBlock(blockNumber uint64, namespace string) []suave.Bid { + dbBlockProtoIndexKey := bidByBlockAndProtocolIndexDbKey(blockNumber, namespace) + rawCurrentValues, closer, err := b.db.Get(dbBlockProtoIndexKey) + if err != nil { + return nil + } + + var currentBidIds bidByBlockAndProtocolIndexType + err = json.Unmarshal(rawCurrentValues, ¤tBidIds) + closer.Close() + if err != nil { + return nil + } + + bids := []suave.Bid{} + for _, bidId := range currentBidIds { + bid, err := b.FetchBidById(bidId) + if err == nil { + bids = append(bids, bid) + } + } + + return bids +} diff --git a/suave/cstore/pebble_test.go b/suave/cstore/pebble_test.go new file mode 100644 index 000000000..7cc1c6fc2 --- /dev/null +++ b/suave/cstore/pebble_test.go @@ -0,0 +1,11 @@ +package cstore + +import ( + "testing" +) + +func TestPebbleStore(t *testing.T) { + tmpDir := t.TempDir() + store, _ := NewPebbleStoreBackend(tmpDir) + testBackendStore(t, store) +}