-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Skeleton and basic tests for confidential store engine #25
Conversation
3e2ecc7
to
7144245
Compare
3d12d74
to
09195e1
Compare
cmd/utils/flags.go
Outdated
@@ -516,6 +516,18 @@ var ( | |||
Category: flags.SuaveCategory, | |||
} | |||
|
|||
SuaveConfidentialPubsubRedisEndpointFlag = &cli.StringFlag{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference between these two flags? Could they be merged?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can't be merged - one is the backed for storing data (redis owned by whoever is running the node), and the other is a transport layer - owned by whoever is trusted to run the pubsub. Those are two different redis instances, the transport one is shared among multiple nodes
@@ -0,0 +1,20 @@ | |||
# Build in a stock Go builder container |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we running suavecli as a docker container now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove the whole docker setup for now and we should think about how to best re-introduce it later on
suave/e2e/redis_transport_test.go
Outdated
mrStore2 := miniredis.RunT(t) | ||
mrPubSub := mrStore1 | ||
|
||
fr1 := newFramework(t, WithExecutionNode(), WithRedisStoreBackend(mrStore1.Addr()), WithRedisStoreTransport(mrPubSub.Addr())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to do something like this:
fr1 := newFramework(t, WithExecutionNode(), WithRedisStoreBackend(mrStore1.Addr()), WithRedisStoreTransport(mrPubSub.Addr())) | |
fr1 := newFramework(t, WithExecutionNode(), WithRedisConfidentialStore()) |
The Framework
is intended to provide a high-level abstraction about the deployment of the chain. That includes any setup required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, yeah that's true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the store backend to work how you proposed, but the same thing won't quite work for the transport, as it requires a shared redis. I made a helper function that spins up a miniredis instance and returns an opt that is then passed to both frameworks - see if you like it better this way.
suave/backends/redis_pubsub.go
Outdated
ch := make(chan suave.DAMessage, 16) | ||
|
||
go func() { | ||
for r.ctx.Err() == nil && ctx.Err() == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if one of these errors is not nil, would it get logged somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally on this function, i think we might need to add reconnect logic in case the redis connection drops 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adjusted how context is handled
@@ -25,14 +25,14 @@ contract AnyBidContract { | |||
|
|||
contract BundleBidContract is AnyBidContract { | |||
|
|||
function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers) external payable returns (bytes memory) { | |||
function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores) external payable returns (bytes memory) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we treat bidAllowedStores
similar to executionNodes specified when the user originally sends tx and keep out of this level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, so the big difference is that the bid's Id (hash) has to take into account the allowed stores -- unlike execution node address, more like the allowed peekers.
I think that having in mind the direction in which we move, this will supersede execution node address, and I think it should be at the contract level
suave/core/types.go
Outdated
} | ||
|
||
type ConfidentialEthBackend interface { | ||
BuildEthBlock(ctx context.Context, args *BuildBlockArgs, txs types.Transactions) (*engine.ExecutionPayloadEnvelope, error) | ||
BuildEthBlockFromBundles(ctx context.Context, args *BuildBlockArgs, bundles []types.SBundle) (*engine.ExecutionPayloadEnvelope, error) | ||
} | ||
|
||
type BuildBlockArgs = types.BuildBlockArgs | ||
type PubSub interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Topic
is a better name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to StoreTransportTopic
suave/backends/redis_pubsub.go
Outdated
} | ||
) | ||
|
||
type RedisPubSub struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you also need to include a UUID from the node. Last time I worked with redis pubsub, it would pub it's own message back to itself and you are meant to filter these out with the UUID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking to maybe set it to the execution node address, but that would mean nodes can't share it. Uuid is easily spoofed, but we could use both as a way to cut out the messages we send
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check if you like it better
|
||
func (r *RedisStoreBackend) Store(bid suave.Bid, caller common.Address, key string, value []byte) (suave.Bid, error) { | ||
storeKey := formatRedisBidValueKey(bid.Id, key) | ||
err := r.client.Set(r.ctx, storeKey, string(value), time.Second).Err() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're not putting a TTL/expiry on this. I think that might be okay for now, I would rather worry about Redis getting too large than it expiring out from under me while testing. But we do need to come to consensus on some TTL before testnet and advertise publicly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I've made it a day for now
fd926a8
to
c054246
Compare
suave/backends/miniredis_backends.go
Outdated
return fmt.Sprintf("bid-%x", bidId) | ||
} | ||
|
||
formatMiniredisBidValueKey = func(bid suave.Bid, key string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reuse across backends?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't think there's any benefit to making miniredis different. Unified
"github.com/go-redis/redis/v8" | ||
) | ||
|
||
var ffStoreTTL = 24 * time.Hour |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
24
could be an env var 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd avoid giving a false sense of stability by making this an env var - this is subject to change soon
|
||
func (r *RedisStoreBackend) Stop() error { | ||
if r.cancel == nil || r.client == nil { | ||
panic("Stop() called before Start()") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm perhaps better return an error instead of panic
? i think panic shouldn't be used in live code if at all possible (and if used the function should be prefixed with Must
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this case, you could just silently return too
return nil | ||
} | ||
|
||
func (r *RedisStoreBackend) Stop() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if error result is unused, remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's implementing the node.Lifecycle
interface
I'll maybe log the error if any appear?
suave/backends/redis_transport.go
Outdated
log.Trace("Rebids pubsub: publishing", "message", message) | ||
data, err := json.Marshal(message) | ||
if err != nil { | ||
panic(fmt.Errorf("could not marshal message: %w", err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no panic please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand, under what circumstances would this be the case?
Added a log + early return
suave/backends/redis_transport.go
Outdated
|
||
func (r *RedisPubSubTransport) Stop() error { | ||
if r.cancel == nil || r.client == nil { | ||
panic("Stop() called before Start()") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no panic please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this one I'm kind of on the fence. This should never be the case, and would only appear if someone introduced a bug - in which case a panic is the quickest way to spot the error.
On the other hand it's not critical so it'd be better to not terminate.
I'll return errors
func calculateBidId(bid types.Bid) (types.BidId, error) { | ||
copy(bid.Id[:], emptyId[:]) | ||
|
||
body, err := json.Marshal(bid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker but I would consider doing keccak(rlp(bid))
instead to have the same standard as the other hashed objects in the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd defer to a later point, probably once we'll consider having another implementation - right now the id is localized to this function, and I'm not sure if bid will rlp-encode straight away
transportTopic StoreTransportTopic | ||
mempool MempoolBackend | ||
|
||
daSigner DASigner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for clarity can we add a comment that "DA = data availability"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LSTM
@@ -298,26 +303,15 @@ type ConfidentialEthBackend interface { | |||
|
|||
The Confidential Store is an integral part of the SUAVE chain, designed to facilitate secure and privacy-preserving transactions and smart contract interactions. It functions as a key-value store where users can safely store and retrieve confidential data related to their bids. The Confidential Store restricts access (both reading and writing) only to the allowed peekers of each bid, allowing developers to define the entire data model of their application! | |||
|
|||
The current, and certainly not final, implementation of the Confidential Store is managed by the `LocalConfidentialStore` struct. It provides thread-safe access to the bids' confidential data. The `LocalConfidentialStore` struct is composed of a mutex lock and a map of bid data, `ACData`, indexed by a `BidId`. | |||
The current, and certainly not final, implementation of the Confidential Store is managed by the `ConfidentialStoreEngine`. The engine consists of a storage backend, which holds the raw data, and a transport topic, which relays synchronization messages between nodes. | |||
We provide two storage backends to the confidential store engine: the `LocalConfidentialStore`, storing data in memory in a simple dictionary, and `RedisStoreBackend`, storing data in redis. To enable redis as the storage backed, pass redis endpoint via `--suave.confidential.redis-store-endpoint`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps just add a note that Redis is only intended for first testnet and as an upgrade path for a future p2p based protocol
📝 Summary
Readme diff: https://github.com/flashbots/suave-geth/pull/25/files?short_path=b335630#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5
ConfidentialStoreEngine
which manages confidential store interfaces (storage and synchronization).Store
is now propagated through the transport to other nodes.AccountManager
-AccountManagerDASigner
. This signer signs messages exchanged through confidential store transport layer.Changes to the Bid:
Id
of the Bid structure to be uuidv5 (SHA-1) of the bidSalt
to the Bid structure, a random uuidAllowedStores
to the Bid structure, an allowlist for which execution nodes are able to modify the bid's data in confidential storeOther notable changes:
SuaveContext
in place of the previousSuaveExecutionBackend
. RemovesSetConfidentialInputs
function.ConfidentialStoreBackend
s, moves all of the validation logic to the newConfidentialStoreEngine
📚 References