From 892e2e11ba2735cdbc7d7ef694b8942dadaf0bdd Mon Sep 17 00:00:00 2001 From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:58:17 +0200 Subject: [PATCH] Transactional store (#57) * Adds TransactionalStore- a structure that batches updates to the confidential engine * Rewrites ConfidentialStoreEngine to account for batched updates - InitializeBid now does not store anything on the backend, and Store function is now absent. Adds Finalize function to the engine, which writes a batch of updates to the store backend * Unifies MEVM execution in eth api behind runMEVM function * Removes transactions from ConfidentialStore interface, passing transaction around is now managed by TransactionalStore * Splits EthAPIBackend.GetEVM into two - suave-only GetMEVM and non-suave only GetEVM * EthAPIBackend.GetMEVM now also returns a finalization function which causes the batched writes to confidential store to be applied * Minor QoL improvements in testing --- core/vm/contracts_suave.go | 10 +- core/vm/contracts_suave_eth.go | 14 +- core/vm/contracts_suave_test.go | 34 ++- core/vm/suave.go | 8 +- eth/api_backend.go | 38 ++- eth/backend.go | 25 +- internal/ethapi/api.go | 146 +++++---- internal/ethapi/api_test.go | 5 +- internal/ethapi/backend.go | 3 +- internal/ethapi/transaction_args_test.go | 5 +- les/api_backend.go | 6 +- suave/backends/redis_backends_test.go | 51 ++-- suave/backends/redis_transport.go | 26 +- suave/backends/transactional_store_test.go | 82 ++++++ suave/core/engine.go | 325 +++++++++++---------- suave/core/engine_test.go | 23 +- suave/core/transactional_store.go | 113 +++++++ suave/core/types.go | 33 ++- suave/e2e/workflow_test.go | 75 +++-- 19 files changed, 675 insertions(+), 347 deletions(-) create mode 100644 suave/backends/transactional_store_test.go create mode 100644 suave/core/transactional_store.go diff --git a/core/vm/contracts_suave.go b/core/vm/contracts_suave.go index 07580e4c60..44db13c5f7 100644 --- a/core/vm/contracts_suave.go +++ b/core/vm/contracts_suave.go @@ -136,7 +136,7 @@ func (c *confStoreStore) runImpl(suaveContext *SuaveContext, bidId suave.BidId, confStorePrecompileStoreMeter.Mark(int64(len(data))) } - _, err := suaveContext.Backend.ConfidentialStoreEngine.Store(bidId, suaveContext.ConfidentialComputeRequestTx, caller, key, data) + _, err := suaveContext.Backend.ConfidentialStore.Store(bidId, caller, key, data) if err != nil { return err } @@ -195,7 +195,7 @@ func (c *confStoreRetrieve) runImpl(suaveContext *SuaveContext, bidId suave.BidI } } - data, err := suaveContext.Backend.ConfidentialStoreEngine.Retrieve(bidId, caller, key) + data, err := suaveContext.Backend.ConfidentialStore.Retrieve(bidId, caller, key) if err != nil { return []byte(err.Error()), err } @@ -250,13 +250,13 @@ func (c *newBid) runImpl(suaveContext *SuaveContext, version string, decryptionC panic("newBid: source transaction not present") } - bid, err := suaveContext.Backend.ConfidentialStoreEngine.InitializeBid(types.Bid{ + bid, err := suaveContext.Backend.ConfidentialStore.InitializeBid(types.Bid{ Salt: suave.RandomBidId(), DecryptionCondition: decryptionCondition, AllowedPeekers: allowedPeekers, AllowedStores: allowedStores, Version: version, // TODO : make generic - }, suaveContext.ConfidentialComputeRequestTx) + }) if err != nil { return nil, err } @@ -300,7 +300,7 @@ func (c *fetchBids) RunConfidential(suaveContext *SuaveContext, input []byte) ([ } func (c *fetchBids) runImpl(suaveContext *SuaveContext, targetBlock uint64, namespace string) ([]types.Bid, error) { - bids1 := suaveContext.Backend.ConfidentialStoreEngine.FetchBidsByProtocolAndBlock(targetBlock, namespace) + bids1 := suaveContext.Backend.ConfidentialStore.FetchBidsByProtocolAndBlock(targetBlock, namespace) bids := make([]types.Bid, 0, len(bids1)) for _, bid := range bids1 { diff --git a/core/vm/contracts_suave_eth.go b/core/vm/contracts_suave_eth.go index a44ee04362..893668ac46 100644 --- a/core/vm/contracts_suave_eth.go +++ b/core/vm/contracts_suave_eth.go @@ -202,7 +202,7 @@ func (c *buildEthBlock) RunConfidential(suaveContext *SuaveContext, input []byte func (c *buildEthBlock) runImpl(suaveContext *SuaveContext, blockArgs types.BuildBlockArgs, bidId types.BidId, namespace string) ([]byte, []byte, error) { bidIds := [][16]byte{} // first check for merged bid, else assume regular bid - if mergedBidsBytes, err := suaveContext.Backend.ConfidentialStoreEngine.Retrieve(bidId, buildEthBlockAddress, "default:v0:mergedBids"); err == nil { + if mergedBidsBytes, err := suaveContext.Backend.ConfidentialStore.Retrieve(bidId, buildEthBlockAddress, "default:v0:mergedBids"); err == nil { unpacked, err := bidIdsAbi.Inputs.Unpack(mergedBidsBytes) if err != nil { @@ -217,7 +217,7 @@ func (c *buildEthBlock) runImpl(suaveContext *SuaveContext, blockArgs types.Buil for i, bidId := range bidIds { var err error - bid, err := suaveContext.Backend.ConfidentialStoreEngine.FetchBidById(bidId) + bid, err := suaveContext.Backend.ConfidentialStore.FetchBidById(bidId) if err != nil { return nil, nil, fmt.Errorf("could not fetch bid id %v: %w", bidId, err) } @@ -229,7 +229,7 @@ func (c *buildEthBlock) runImpl(suaveContext *SuaveContext, blockArgs types.Buil switch bid.Version { case "mevshare:v0:matchBids": // fetch the matched ids and merge the bundle - matchedBundleIdsBytes, err := suaveContext.Backend.ConfidentialStoreEngine.Retrieve(bid.Id, buildEthBlockAddress, "mevshare:v0:mergedBids") + matchedBundleIdsBytes, err := suaveContext.Backend.ConfidentialStore.Retrieve(bid.Id, buildEthBlockAddress, "mevshare:v0:mergedBids") if err != nil { return nil, nil, fmt.Errorf("could not retrieve bid ids data for bid %v, from cdas: %w", bid, err) } @@ -241,7 +241,7 @@ func (c *buildEthBlock) runImpl(suaveContext *SuaveContext, blockArgs types.Buil matchBidIds := unpackedBidIds[0].([][16]byte) - userBundleBytes, err := suaveContext.Backend.ConfidentialStoreEngine.Retrieve(matchBidIds[0], buildEthBlockAddress, "mevshare:v0:ethBundles") + userBundleBytes, err := suaveContext.Backend.ConfidentialStore.Retrieve(matchBidIds[0], buildEthBlockAddress, "mevshare:v0:ethBundles") if err != nil { return nil, nil, fmt.Errorf("could not retrieve bundle data for bidId %v: %w", matchBidIds[0], err) } @@ -251,7 +251,7 @@ func (c *buildEthBlock) runImpl(suaveContext *SuaveContext, blockArgs types.Buil return nil, nil, fmt.Errorf("could not unmarshal user bundle data for bidId %v: %w", matchBidIds[0], err) } - matchBundleBytes, err := suaveContext.Backend.ConfidentialStoreEngine.Retrieve(matchBidIds[1], buildEthBlockAddress, "mevshare:v0:ethBundles") + matchBundleBytes, err := suaveContext.Backend.ConfidentialStore.Retrieve(matchBidIds[1], buildEthBlockAddress, "mevshare:v0:ethBundles") if err != nil { return nil, nil, fmt.Errorf("could not retrieve match bundle data for bidId %v: %w", matchBidIds[1], err) } @@ -266,7 +266,7 @@ func (c *buildEthBlock) runImpl(suaveContext *SuaveContext, blockArgs types.Buil mergedBundles = append(mergedBundles, userBundle) case "mevshare:v0:unmatchedBundles": - bundleBytes, err := suaveContext.Backend.ConfidentialStoreEngine.Retrieve(bid.Id, buildEthBlockAddress, "mevshare:v0:ethBundles") + bundleBytes, err := suaveContext.Backend.ConfidentialStore.Retrieve(bid.Id, buildEthBlockAddress, "mevshare:v0:ethBundles") if err != nil { return nil, nil, fmt.Errorf("could not retrieve bundle data for bidId %v, from cdas: %w", bid.Id, err) } @@ -277,7 +277,7 @@ func (c *buildEthBlock) runImpl(suaveContext *SuaveContext, blockArgs types.Buil } mergedBundles = append(mergedBundles, bundle) case "default:v0:ethBundles": - bundleBytes, err := suaveContext.Backend.ConfidentialStoreEngine.Retrieve(bid.Id, buildEthBlockAddress, "default:v0:ethBundles") + bundleBytes, err := suaveContext.Backend.ConfidentialStore.Retrieve(bid.Id, buildEthBlockAddress, "default:v0:ethBundles") if err != nil { return nil, nil, fmt.Errorf("could not retrieve bundle data for bidId %v, from cdas: %w", bid.Id, err) } diff --git a/core/vm/contracts_suave_test.go b/core/vm/contracts_suave_test.go index ec1e27d6d6..906ba41323 100644 --- a/core/vm/contracts_suave_test.go +++ b/core/vm/contracts_suave_test.go @@ -77,18 +77,19 @@ func TestSuavePrecompileStub(t *testing.T) { // This test ensures that the Suave precompile stubs work as expected // for encoding/decoding. mockSuaveBackend := &mockSuaveBackend{} - stubEngine, err := suave.NewConfidentialStoreEngine(mockSuaveBackend, mockSuaveBackend, suave.MockSigner{}, suave.MockChainSigner{}) - require.NoError(t, err) + stubEngine := suave.NewConfidentialStoreEngine(mockSuaveBackend, mockSuaveBackend, suave.MockSigner{}, suave.MockChainSigner{}) + + reqTx := types.NewTx(&types.ConfidentialComputeRequest{ + ExecutionNode: common.Address{}, + Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), + }) suaveContext := SuaveContext{ Backend: &SuaveExecutionBackend{ - ConfidentialStoreEngine: stubEngine, - ConfidentialEthBackend: mockSuaveBackend, + ConfidentialStore: stubEngine.NewTransactionalStore(reqTx), + ConfidentialEthBackend: mockSuaveBackend, }, - ConfidentialComputeRequestTx: types.NewTx(&types.ConfidentialComputeRequest{ - ExecutionNode: common.Address{}, - Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), - }), + ConfidentialComputeRequestTx: reqTx, } statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -141,22 +142,23 @@ func TestSuavePrecompileStub(t *testing.T) { func newTestBackend(t *testing.T) *suaveRuntime { confStore := backends.NewLocalConfidentialStore() - confEngine, err := suave.NewConfidentialStoreEngine(confStore, &suave.MockTransport{}, suave.MockSigner{}, suave.MockChainSigner{}) - require.NoError(t, err) + confEngine := suave.NewConfidentialStoreEngine(confStore, &suave.MockTransport{}, suave.MockSigner{}, suave.MockChainSigner{}) require.NoError(t, confEngine.Start()) t.Cleanup(func() { confEngine.Stop() }) + reqTx := types.NewTx(&types.ConfidentialComputeRequest{ + ExecutionNode: common.Address{}, + Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), + }) + b := &suaveRuntime{ suaveContext: &SuaveContext{ Backend: &SuaveExecutionBackend{ - ConfidentialStoreEngine: confEngine, - ConfidentialEthBackend: &mockSuaveBackend{}, + ConfidentialStore: confEngine.NewTransactionalStore(reqTx), + ConfidentialEthBackend: &mockSuaveBackend{}, }, - ConfidentialComputeRequestTx: types.NewTx(&types.ConfidentialComputeRequest{ - ExecutionNode: common.Address{}, - Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), - }), + ConfidentialComputeRequestTx: reqTx, }, } return b diff --git a/core/vm/suave.go b/core/vm/suave.go index e58810122e..889dee0d74 100644 --- a/core/vm/suave.go +++ b/core/vm/suave.go @@ -14,8 +14,8 @@ import ( // ConfidentialStore represents the API for the confidential store // required by Suave runtime. type ConfidentialStore interface { - InitializeBid(bid types.Bid, creationTx *types.Transaction) (types.Bid, error) - Store(bidId suave.BidId, sourceTx *types.Transaction, caller common.Address, key string, value []byte) (suave.Bid, error) + InitializeBid(bid types.Bid) (types.Bid, error) + Store(bidId suave.BidId, caller common.Address, key string, value []byte) (suave.Bid, error) Retrieve(bid types.BidId, caller common.Address, key string) ([]byte, error) FetchBidById(suave.BidId) (suave.Bid, error) FetchBidsByProtocolAndBlock(blockNumber uint64, namespace string) []suave.Bid @@ -30,8 +30,8 @@ type SuaveContext struct { } type SuaveExecutionBackend struct { - ConfidentialStoreEngine ConfidentialStore - ConfidentialEthBackend suave.ConfidentialEthBackend + ConfidentialStore ConfidentialStore + ConfidentialEthBackend suave.ConfidentialEthBackend } func NewRuntimeSuaveContext(evm *EVM, caller common.Address) *SuaveContext { diff --git a/eth/api_backend.go b/eth/api_backend.go index 0034e33724..3e5cade3e1 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -49,12 +49,13 @@ type EthAPIBackend struct { allowUnprotectedTxs bool eth *Ethereum gpo *gasprice.Oracle - suaveBackend *vm.SuaveExecutionBackend + suaveEngine *suave.ConfidentialStoreEngine + suaveEthBackend suave.ConfidentialEthBackend } -func (b *EthAPIBackend) SuaveBackend() *vm.SuaveExecutionBackend { - // For testing purposes - return b.suaveBackend +// For testing purposes +func (b *EthAPIBackend) SuaveEngine() *suave.ConfidentialStoreEngine { + return b.suaveEngine } // ChainConfig returns the active chain configuration. @@ -253,7 +254,7 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) { if vmConfig == nil { vmConfig = b.eth.blockchain.GetVMConfig() } @@ -265,15 +266,28 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) } - if vmConfig.IsConfidential { - suaveCtxCopy := *suaveCtx - if suaveCtx.Backend == nil { - suaveCtxCopy.Backend = b.suaveBackend - } - return vm.NewConfidentialEVM(suaveCtxCopy, context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error + return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error +} + +func (b *EthAPIBackend) GetMEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error, func() error) { + if vmConfig == nil { + vmConfig = b.eth.blockchain.GetVMConfig() + } + txContext := core.NewEVMTxContext(msg) + var context vm.BlockContext + if blockCtx != nil { + context = *blockCtx } else { - return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error + context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + } + + suaveCtxCopy := *suaveCtx + storeTransaction := b.suaveEngine.NewTransactionalStore(suaveCtx.ConfidentialComputeRequestTx) + suaveCtxCopy.Backend = &vm.SuaveExecutionBackend{ + ConfidentialStore: storeTransaction, + ConfidentialEthBackend: b.suaveEthBackend, } + return vm.NewConfidentialEVM(suaveCtxCopy, context, txContext, state, b.eth.blockchain.Config(), *vmConfig), storeTransaction.Finalize, state.Error } func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { diff --git a/eth/backend.go b/eth/backend.go index 126b3f8ad8..e7f94d7ec0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -101,9 +101,6 @@ type Ethereum struct { lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully - - // Suave fields - ConfidentialStore *suave.ConfidentialStoreEngine } // New creates a new Ethereum object (including the @@ -233,7 +230,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, eth.blockchain.Config(), eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - confidentialStoreBackend := suave_backends.NewRedisStoreBackend(config.Suave.RedisStoreUri) + var confidentialStoreBackend suave.ConfidentialStoreBackend + if config.Suave.RedisStoreUri != "" { + confidentialStoreBackend = suave_backends.NewRedisStoreBackend(config.Suave.RedisStoreUri) + } else { + confidentialStoreBackend = suave_backends.NewLocalConfidentialStore() + } var confidentialStoreTransport suave.StoreTransportTopic if config.Suave.RedisStorePubsubUri != "" { @@ -251,19 +253,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { suaveDaSigner := &suave_backends.AccountManagerDASigner{Manager: eth.AccountManager()} - confidentialStoreEngine, err := suave.NewConfidentialStoreEngine(confidentialStoreBackend, confidentialStoreTransport, suaveDaSigner, types.LatestSigner(chainConfig)) - if err != nil { - return nil, err - } - - eth.ConfidentialStore = confidentialStoreEngine - stack.RegisterLifecycle(confidentialStoreEngine) + confidentialStoreEngine := suave.NewConfidentialStoreEngine(confidentialStoreBackend, confidentialStoreTransport, suaveDaSigner, types.LatestSigner(chainConfig)) - suaveBackend := &vm.SuaveExecutionBackend{ - ConfidentialStoreEngine: confidentialStoreEngine, - ConfidentialEthBackend: suaveEthBackend, - } - eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil, suaveBackend} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil, confidentialStoreEngine, suaveEthBackend} if eth.APIBackend.allowUnprotectedTxs { log.Info("Unprotected transactions allowed") } @@ -291,6 +283,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { stack.RegisterAPIs(eth.APIs()) stack.RegisterProtocols(eth.Protocols()) stack.RegisterLifecycle(eth) + stack.RegisterLifecycle(confidentialStoreEngine) // Successful startup; push a marker and check previous unclean shutdowns. eth.shutdownTracker.MarkStartup() diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b21e49b306..415c6981b2 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -47,6 +47,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + suave "github.com/ethereum/go-ethereum/suave/core" "github.com/tyler-smith/go-bip39" ) @@ -1021,20 +1022,46 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash blockOverrides.Apply(&blockCtx) } - var suaveCtx *vm.SuaveContext if args.IsConfidential { - suaveCtx = &vm.SuaveContext{ - Backend: nil, // Set by backend, would be better to set it here already - ConfidentialComputeRequestTx: args.ToTransaction(), + if args.ExecutionNode == nil { + acc := b.AccountManager().Accounts()[0] + args.ExecutionNode = &acc } + tx := args.ToTransaction() + + state, header, err := b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if state == nil || err != nil { + return nil, err + } + + msg := &core.Message{ + Nonce: tx.Nonce(), + GasLimit: tx.Gas(), + GasPrice: new(big.Int), + GasFeeCap: new(big.Int), + GasTipCap: new(big.Int), + To: tx.To(), + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + SkipAccountChecks: true, + } + + var confidentialInputs []byte if args.ConfidentialInputs != nil { - suaveCtx.ConfidentialInputs = []byte(*args.ConfidentialInputs) + confidentialInputs = []byte(*args.ConfidentialInputs) } + + _, result, finalize, err := runMEVM(ctx, b, state, header, tx, msg, confidentialInputs) + if err := finalize(); err != suave.ErrUnsignedFinalize { + return nil, err + } + return result, err } - vmConfig := vm.Config{NoBaseFee: true, IsConfidential: args.IsConfidential} - evm, vmError := b.GetEVM(ctx, msg, state, header, &vmConfig, &blockCtx, suaveCtx) + vmConfig := vm.Config{NoBaseFee: true, IsConfidential: false} + evm, vmError := b.GetEVM(ctx, msg, state, header, &vmConfig, &blockCtx) // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1556,7 +1583,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) config := vm.Config{Tracer: tracer, NoBaseFee: true} - vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil, nil) + vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil) res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) @@ -1829,12 +1856,31 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr } if tx.Type() == types.ConfidentialComputeRequestTxType { - // TODO: this is a huge dos vector! - log.Info("received confidential compute request tx", "tx", tx.Hash()) - tx, err := s.executeConfidentialCall(ctx, tx, confidential) + state, header, err := s.b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if state == nil || err != nil { + return common.Hash{}, err + } + + msg, err := core.TransactionToMessage(tx, s.signer, header.BaseFee) + if err != nil { + return common.Hash{}, err + } + + var confidentialInputs []byte + if confidential != nil { + confidentialInputs = []byte(*confidential) + } + + ntx, _, finalize, err := runMEVM(ctx, s.b, state, header, signed, msg, confidentialInputs) if err != nil { + return common.Hash{}, err + } + + if err = finalize(); err != nil { + log.Error("could not finalize confidential store", "err", err) return tx.Hash(), err } + signed = ntx } return SubmitTransaction(ctx, s.b, signed) } @@ -1865,10 +1911,27 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B } if tx.Type() == types.ConfidentialComputeRequestTxType { - // TODO: only if not yet signed - // TODO: this is a huge dos vector! - ntx, err := s.executeConfidentialCall(ctx, tx, confidential) + state, header, err := s.b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if state == nil || err != nil { + return common.Hash{}, err + } + + msg, err := core.TransactionToMessage(tx, s.signer, header.BaseFee) if err != nil { + return common.Hash{}, err + } + + var confidentialInputs []byte + if confidential != nil { + confidentialInputs = []byte(*confidential) + } + + ntx, _, finalize, err := runMEVM(ctx, s.b, state, header, tx, msg, confidentialInputs) + if err != nil { + return tx.Hash(), err + } + if err = finalize(); err != nil { + log.Error("could not finalize confidential store", "err", err) return tx.Hash(), err } tx = ntx @@ -1877,52 +1940,33 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B return SubmitTransaction(ctx, s.b, tx) } -func (s *TransactionAPI) executeConfidentialCall(ctx context.Context, tx *types.Transaction, confidential *hexutil.Bytes) (*types.Transaction, error) { - defer func(start time.Time) { - log.Info("Executing confidential compute request call finished", "runtime", time.Since(start)) - }(time.Now()) +// TODO: should be its own api +func runMEVM(ctx context.Context, b Backend, state *state.StateDB, header *types.Header, tx *types.Transaction, msg *core.Message, confidentialInputs []byte) (*types.Transaction, *core.ExecutionResult, func() error, error) { + var cancel context.CancelFunc + ctx, cancel = context.WithCancel(ctx) + defer cancel() // TODO: copy the inner, but only once confidentialRequestTx, ok := types.CastTxInner[*types.ConfidentialComputeRequest](tx) if !ok { - return nil, errors.New("invalid transaction passed") + return nil, nil, nil, errors.New("invalid transaction passed") } // Look up the wallet containing the requested execution node account := accounts.Account{Address: confidentialRequestTx.ExecutionNode} - wallet, err := s.b.AccountManager().Find(account) + wallet, err := b.AccountManager().Find(account) if err != nil { - return nil, err + return nil, nil, nil, err } - state, header, err := s.b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) - if state == nil || err != nil { - return nil, err - } - - var cancel context.CancelFunc - ctx, cancel = context.WithCancel(ctx) - - // Make sure the context is cancelled when the call has completed - // this makes sure resources are cleaned up. - defer cancel() - - // Get a new instance of the EVM. - msg, err := core.TransactionToMessage(tx, s.signer, header.BaseFee) - if err != nil { - return nil, err - } - blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) suaveCtx := vm.SuaveContext{ ConfidentialComputeRequestTx: tx, + ConfidentialInputs: confidentialInputs, } - if confidential != nil { - suaveCtx.ConfidentialInputs = []byte(*confidential) - } - - evm, vmError := s.b.GetEVM(ctx, msg, state, header, &vm.Config{IsConfidential: true}, &blockCtx, &suaveCtx) + evm, storeFinalize, vmError := b.GetMEVM(ctx, msg, state, header, &vm.Config{IsConfidential: true}, &blockCtx, &suaveCtx) // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1938,17 +1982,17 @@ func (s *TransactionAPI) executeConfidentialCall(ctx context.Context, tx *types. result, err := core.ApplyMessage(evm, msg, gp) // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { - return nil, fmt.Errorf("execution aborted") + return nil, nil, nil, fmt.Errorf("execution aborted") } if err != nil { - return tx, fmt.Errorf("err: %w (supplied gas %d)", err, msg.GasLimit) + return tx, nil, nil, fmt.Errorf("err: %w (supplied gas %d)", err, msg.GasLimit) } if err := vmError(); err != nil { - return nil, err + return nil, nil, nil, err } if result.Failed() { - return nil, fmt.Errorf("%w: %s", result.Err, hexutil.Encode(result.Revert())) + return nil, nil, nil, fmt.Errorf("%w: %s", result.Err, hexutil.Encode(result.Revert())) } // Check for call in return @@ -1965,13 +2009,13 @@ func (s *TransactionAPI) executeConfidentialCall(ctx context.Context, tx *types. suaveResultTxData := &types.SuaveTransaction{ExecutionNode: confidentialRequestTx.ExecutionNode, ConfidentialComputeRequest: *tx, ConfidentialComputeResult: computeResult} - signed, err := wallet.SignTx(account, types.NewTx(suaveResultTxData), s.b.ChainConfig().ChainID) + signed, err := wallet.SignTx(account, types.NewTx(suaveResultTxData), confidentialRequestTx.ChainID) if err != nil { - return nil, err + return nil, nil, nil, err } // will copy the inner tx again! - return signed, nil + return signed, result, storeFinalize, nil } // Sign calculates an ECDSA signature for: diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 2e7bc2d9e8..aa3eec948f 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -286,7 +286,7 @@ func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.R panic("implement me") } func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { panic("implement me") } -func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error) { +func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) (*vm.EVM, func() error) { vmError := func() error { return nil } if vmConfig == nil { vmConfig = b.chain.GetVMConfig() @@ -298,6 +298,9 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state } return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig), vmError } +func (b testBackend) GetMEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error, func() error) { + return nil, nil, nil +} func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { panic("implement me") } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 7dc532127d..7eca45922a 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -68,7 +68,8 @@ type Backend interface { PendingBlockAndReceipts() (*types.Block, types.Receipts) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetTd(ctx context.Context, hash common.Hash) *big.Int - GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error) + GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) + GetMEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error, func() error) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 77049c9174..a3f547f653 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -306,9 +306,12 @@ func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number return nil, nil } func (b *backendMock) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error) { +func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) { return nil, nil } +func (b *backendMock) GetMEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error, func() error) { + return nil, nil, nil +} func (b *backendMock) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { return nil } func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { return nil diff --git a/les/api_backend.go b/les/api_backend.go index b91f347e43..71c3ea3b0c 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -185,7 +185,7 @@ func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error) { +func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) { if vmConfig == nil { vmConfig = new(vm.Config) } @@ -197,6 +197,10 @@ func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *st return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error } +func (b *LesApiBackend) GetMEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext, suaveCtx *vm.SuaveContext) (*vm.EVM, func() error, func() error) { + return nil, nil, nil +} + func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { return b.eth.txPool.Add(ctx, signedTx) } diff --git a/suave/backends/redis_backends_test.go b/suave/backends/redis_backends_test.go index 4bbc53be5b..cc653f7fd2 100644 --- a/suave/backends/redis_backends_test.go +++ b/suave/backends/redis_backends_test.go @@ -9,6 +9,7 @@ import ( "github.com/alicebob/miniredis/v2" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" suave "github.com/ethereum/go-ethereum/suave/core" "github.com/stretchr/testify/require" ) @@ -24,13 +25,15 @@ func TestRedisTransport(t *testing.T) { t.Cleanup(cancel) daMsg := suave.DAMessage{ - Bid: suave.Bid{ - Id: suave.BidId{0x42}, - DecryptionCondition: uint64(13), - AllowedPeekers: []common.Address{{0x41, 0x39}}, - Version: string("vv"), - }, - Value: suave.Bytes{}, + StoreWrites: []suave.StoreWrite{{ + Bid: suave.Bid{ + Id: suave.BidId{0x42}, + DecryptionCondition: uint64(13), + AllowedPeekers: []common.Address{{0x41, 0x39}}, + Version: string("vv"), + }, + Value: suave.Bytes{}, + }}, Signature: []byte{}, } @@ -49,7 +52,7 @@ func TestRedisTransport(t *testing.T) { case <-time.After(5 * time.Millisecond): } - daMsg.Bid.Id[0] = 0x43 + daMsg.StoreWrites[0].Bid.Id[0] = 0x43 redisPubSub.Publish(daMsg) select { @@ -68,25 +71,23 @@ func TestEngineOnRedis(t *testing.T) { redisPubSub1 := NewRedisPubSubTransport(mrPubSub.Addr()) redisStoreBackend1 := NewRedisStoreBackend(mrStore1.Addr()) - engine1, err := suave.NewConfidentialStoreEngine(redisStoreBackend1, redisPubSub1, suave.MockSigner{}, suave.MockChainSigner{}) - require.NoError(t, err) - + engine1 := suave.NewConfidentialStoreEngine(redisStoreBackend1, redisPubSub1, suave.MockSigner{}, suave.MockChainSigner{}) require.NoError(t, engine1.Start()) t.Cleanup(func() { engine1.Stop() }) redisPubSub2 := NewRedisPubSubTransport(mrPubSub.Addr()) redisStoreBackend2 := NewRedisStoreBackend(mrStore2.Addr()) - engine2, err := suave.NewConfidentialStoreEngine(redisStoreBackend2, redisPubSub2, suave.MockSigner{}, suave.MockChainSigner{}) - require.NoError(t, err) - + engine2 := suave.NewConfidentialStoreEngine(redisStoreBackend2, redisPubSub2, suave.MockSigner{}, suave.MockChainSigner{}) require.NoError(t, engine2.Start()) t.Cleanup(func() { engine2.Stop() }) - dummyCreationTx := types.NewTx(&types.ConfidentialComputeRequest{ + testKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + dummyCreationTx, err := types.SignTx(types.NewTx(&types.ConfidentialComputeRequest{ ExecutionNode: common.Address{}, Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), - }) + }), types.NewSuaveSigner(new(big.Int)), testKey) + require.NoError(t, err) // Make sure a store to engine1 is propagated to endine2 through redis->miniredis transport bid, err := engine1.InitializeBid(types.Bid{ @@ -106,8 +107,12 @@ func TestEngineOnRedis(t *testing.T) { t.Cleanup(cancel) // Trigger propagation - _, err = engine1.Store(bid.Id, dummyCreationTx, bid.AllowedPeekers[0], "xx", []byte{0x43, 0x14}) - + err = engine1.Finalize(dummyCreationTx, nil, []suave.StoreWrite{{ + Bid: bid, + Caller: bid.AllowedPeekers[0], + Key: "xx", + Value: []byte{0x43, 0x14}, + }}) require.NoError(t, err) time.Sleep(10 * time.Millisecond) @@ -128,15 +133,17 @@ func TestEngineOnRedis(t *testing.T) { submittedBidJson, err := json.Marshal(submittedBid) require.NoError(t, err) + // require.NoError(t, engine1.Finalize(dummyCreationTx)) + select { case msg := <-subch: - rececivedBidJson, err := json.Marshal(msg.Bid) + rececivedBidJson, err := json.Marshal(msg.StoreWrites[0].Bid) require.NoError(t, err) require.Equal(t, submittedBidJson, rececivedBidJson) - require.Equal(t, "xx", msg.Key) - require.Equal(t, suave.Bytes{0x43, 0x14}, msg.Value) - require.Equal(t, bid.AllowedPeekers[0], msg.Caller) + require.Equal(t, "xx", msg.StoreWrites[0].Key) + require.Equal(t, suave.Bytes{0x43, 0x14}, msg.StoreWrites[0].Value) + require.Equal(t, bid.AllowedPeekers[0], msg.StoreWrites[0].Caller) case <-time.After(20 * time.Millisecond): t.Error("did not receive expected message") } diff --git a/suave/backends/redis_transport.go b/suave/backends/redis_transport.go index 23e93f43cd..01451bcc18 100644 --- a/suave/backends/redis_transport.go +++ b/suave/backends/redis_transport.go @@ -93,26 +93,34 @@ func (r *RedisPubSubTransport) Subscribe() (<-chan suave.DAMessage, context.Canc } var msg suave.DAMessage - err = json.Unmarshal([]byte(rmsg.Payload), &msg) + msgBytes := common.Hex2Bytes(rmsg.Payload) if err != nil { - log.Trace("Redis pubsub: could not parse message from subscription", "err", err, "msg", rmsg.Payload) + log.Info("Redis pubsub: could not decode message from subscription", "err", err, "msg", rmsg.Payload) + continue + } + + err = json.Unmarshal(msgBytes, &msg) + if err != nil { + log.Info("Redis pubsub: could not parse message from subscription", "err", err, "msg", rmsg.Payload) continue } // For some reason the caller, key, and value fields are not parsed correctly // TODO: debug m := make(map[string]interface{}) - err = json.Unmarshal([]byte(rmsg.Payload), &m) + err = json.Unmarshal(msgBytes, &m) if err != nil { - log.Trace("Redis pubsub: could not parse message from subscription", "err", err, "msg", rmsg.Payload) + log.Info("Redis pubsub: could not parse message from subscription", "err", err, "msg", rmsg.Payload) continue } - msg.Caller = common.HexToAddress(m["caller"].(string)) - msg.Key = m["key"].(string) - msg.Value = common.FromHex(m["value"].(string)) + /* + msg.Caller = common.HexToAddress(m["caller"].(string)) + msg.Key = m["key"].(string) + msg.Value = common.FromHex(m["value"].(string)) + */ - log.Debug("Redis pubsub: new message", "msg", msg) + log.Info("Redis pubsub: new message", "msg", msg) select { case <-ctx.Done(): log.Info("Redis pubsub: closing subscription") @@ -137,7 +145,7 @@ func (r *RedisPubSubTransport) Publish(message suave.DAMessage) { return } - r.client.Publish(r.ctx, redisUpsertTopic, string(data)) + r.client.Publish(r.ctx, redisUpsertTopic, common.Bytes2Hex(data)) } func connectRedis(redisURI string) (*redis.Client, error) { diff --git a/suave/backends/transactional_store_test.go b/suave/backends/transactional_store_test.go new file mode 100644 index 0000000000..84c46da059 --- /dev/null +++ b/suave/backends/transactional_store_test.go @@ -0,0 +1,82 @@ +package backends + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + suave "github.com/ethereum/go-ethereum/suave/core" + "github.com/stretchr/testify/require" +) + +func TestTransactionalStore(t *testing.T) { + engine := suave.NewConfidentialStoreEngine(NewLocalConfidentialStore(), suave.MockTransport{}, suave.MockSigner{}, suave.MockChainSigner{}) + + testKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + dummyCreationTx, err := types.SignTx(types.NewTx(&types.ConfidentialComputeRequest{ + ExecutionNode: common.Address{0x42}, + Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), + }), types.NewSuaveSigner(new(big.Int)), testKey) + require.NoError(t, err) + + tstore := engine.NewTransactionalStore(dummyCreationTx) + + testBid, err := tstore.InitializeBid(types.Bid{ + Salt: suave.RandomBidId(), + DecryptionCondition: 46, + AllowedStores: []common.Address{{0x42}}, + AllowedPeekers: []common.Address{{0x43}}, + Version: "v0-test", + }) + require.NoError(t, err) + + _, err = tstore.Store(testBid.Id, testBid.AllowedPeekers[0], "xx", []byte{0x44}) + require.NoError(t, err) + + tfetchedBid, err := tstore.FetchBidById(testBid.Id) + require.NoError(t, err) + require.Equal(t, testBid, tfetchedBid.ToInnerBid()) + + require.Empty(t, tstore.FetchBidsByProtocolAndBlock(45, "v0-test")) + require.Empty(t, tstore.FetchBidsByProtocolAndBlock(46, "v1-test")) + + tfetchedBids := tstore.FetchBidsByProtocolAndBlock(46, "v0-test") + require.Equal(t, 1, len(tfetchedBids)) + require.Equal(t, testBid, tfetchedBids[0].ToInnerBid()) + + _, err = tstore.Retrieve(testBid.Id, testBid.AllowedPeekers[0], "xy") + require.Error(t, err) + + _, err = tstore.Retrieve(suave.RandomBidId(), testBid.AllowedPeekers[0], "xx") + require.Error(t, err) + + _, err = tstore.Retrieve(testBid.Id, testBid.AllowedStores[0], "xx") + require.Error(t, err) + + tretrieved, err := tstore.Retrieve(testBid.Id, testBid.AllowedPeekers[0], "xx") + require.NoError(t, err) + require.Equal(t, []byte{0x44}, tretrieved) + + // Not finalized, engine should return empty + _, err = engine.FetchBidById(testBid.Id) + require.Error(t, err) + require.Empty(t, engine.FetchBidsByProtocolAndBlock(46, "v0-test")) + _, err = engine.Retrieve(testBid.Id, testBid.AllowedPeekers[0], "xx") + require.Error(t, err) + + require.NoError(t, tstore.Finalize()) + + efetchedBid, err := engine.FetchBidById(testBid.Id) + require.NoError(t, err) + require.Equal(t, testBid, efetchedBid.ToInnerBid()) + + efetchedBids := engine.FetchBidsByProtocolAndBlock(46, "v0-test") + require.Equal(t, 1, len(efetchedBids)) + require.Equal(t, testBid, efetchedBids[0].ToInnerBid()) + + eretrieved, err := engine.Retrieve(testBid.Id, testBid.AllowedPeekers[0], "xx") + require.NoError(t, err) + require.Equal(t, []byte{0x44}, eretrieved) +} diff --git a/suave/core/engine.go b/suave/core/engine.go index d4cbadd3af..49bc91484e 100644 --- a/suave/core/engine.go +++ b/suave/core/engine.go @@ -27,6 +27,30 @@ type ConfidentialStoreEngine struct { localAddresses map[common.Address]struct{} } +func NewConfidentialStoreEngine(backend ConfidentialStoreBackend, transportTopic StoreTransportTopic, daSigner DASigner, chainSigner ChainSigner) *ConfidentialStoreEngine { + localAddresses := make(map[common.Address]struct{}) + for _, addr := range daSigner.LocalAddresses() { + localAddresses[addr] = struct{}{} + } + + return &ConfidentialStoreEngine{ + backend: backend, + transportTopic: transportTopic, + daSigner: daSigner, + chainSigner: chainSigner, + storeUUID: uuid.New(), + localAddresses: localAddresses, + } +} + +func (e *ConfidentialStoreEngine) NewTransactionalStore(sourceTx *types.Transaction) *TransactionalStore { + return &TransactionalStore{ + sourceTx: sourceTx, + engine: e, + pendingBids: make(map[BidId]Bid), + } +} + func (e *ConfidentialStoreEngine) Start() error { if err := e.backend.Start(); err != nil { return err @@ -66,32 +90,9 @@ func (e *ConfidentialStoreEngine) Stop() error { return nil } -type DASigner interface { - Sign(account common.Address, data []byte) ([]byte, error) - Sender(data []byte, signature []byte) (common.Address, error) - LocalAddresses() []common.Address -} - -type ChainSigner interface { - Sender(tx *types.Transaction) (common.Address, error) -} - -func NewConfidentialStoreEngine(backend ConfidentialStoreBackend, transportTopic StoreTransportTopic, daSigner DASigner, chainSigner ChainSigner) (*ConfidentialStoreEngine, error) { - localAddresses := make(map[common.Address]struct{}) - for _, addr := range daSigner.LocalAddresses() { - localAddresses[addr] = struct{}{} - } - - engine := &ConfidentialStoreEngine{ - backend: backend, - transportTopic: transportTopic, - daSigner: daSigner, - chainSigner: chainSigner, - storeUUID: uuid.New(), - localAddresses: localAddresses, - } - - return engine, nil +// For testing purposes! +func (e *ConfidentialStoreEngine) Backend() ConfidentialStoreBackend { + return e.backend } func (e *ConfidentialStoreEngine) ProcessMessages() { @@ -113,34 +114,20 @@ func (e *ConfidentialStoreEngine) ProcessMessages() { } } -func ExecutionNodeFromTransaction(tx *types.Transaction) (common.Address, error) { - innerExecutedTx, ok := types.CastTxInner[*types.SuaveTransaction](tx) - if ok { - return innerExecutedTx.ExecutionNode, nil - } - - innerRequestTx, ok := types.CastTxInner[*types.ConfidentialComputeRequest](tx) - if ok { - return innerRequestTx.ExecutionNode, nil - } - - return common.Address{}, fmt.Errorf("transaction is not of confidential type") -} - -func (e *ConfidentialStoreEngine) InitializeBid(bid types.Bid, creationTx *types.Transaction) (types.Bid, error) { +func (e *ConfidentialStoreEngine) InitializeBid(bid types.Bid, creationTx *types.Transaction) (Bid, error) { expectedId, err := calculateBidId(bid) if err != nil { - return types.Bid{}, fmt.Errorf("confidential engine: could not initialize new bid: %w", err) + return Bid{}, fmt.Errorf("confidential engine: could not initialize new bid: %w", err) } if bid.Id == emptyId { bid.Id = expectedId } else if bid.Id != expectedId { // True in some tests, might be time to rewrite them - return types.Bid{}, errors.New("confidential engine:incorrect bid id passed") + return Bid{}, errors.New("confidential engine:incorrect bid id passed") } - daBid := Bid{ + initializedBid := Bid{ Id: bid.Id, Salt: bid.Salt, DecryptionCondition: bid.DecryptionCondition, @@ -150,31 +137,22 @@ func (e *ConfidentialStoreEngine) InitializeBid(bid types.Bid, creationTx *types CreationTx: creationTx, } - bidBytes, err := SerializeBidForSigning(daBid) + bidBytes, err := SerializeBidForSigning(&initializedBid) if err != nil { - return types.Bid{}, fmt.Errorf("confidential engine: could not hash bid for signing: %w", err) + return Bid{}, fmt.Errorf("confidential engine: could not hash bid for signing: %w", err) } signingAccount, err := ExecutionNodeFromTransaction(creationTx) if err != nil { - return types.Bid{}, fmt.Errorf("confidential engine: could not recover execution node from creation transaction: %w", err) + return Bid{}, fmt.Errorf("confidential engine: could not recover execution node from creation transaction: %w", err) } - daBid.Signature, err = e.daSigner.Sign(signingAccount, bidBytes) + initializedBid.Signature, err = e.daSigner.Sign(signingAccount, bidBytes) if err != nil { - return types.Bid{}, fmt.Errorf("confidential engine: could not sign initialized bid: %w", err) + return Bid{}, fmt.Errorf("confidential engine: could not sign initialized bid: %w", err) } - err = e.backend.InitializeBid(daBid) - if err != nil { - return types.Bid{}, fmt.Errorf("confidential engine: store backend failed to initialize bid: %w", err) - } - - return bid, nil -} - -func (e *ConfidentialStoreEngine) StoreBid(bid Bid) error { - return e.backend.InitializeBid(bid) + return initializedBid, nil } func (e *ConfidentialStoreEngine) FetchBidById(bidId BidId) (Bid, error) { @@ -185,82 +163,74 @@ func (e *ConfidentialStoreEngine) FetchBidsByProtocolAndBlock(blockNumber uint64 return e.backend.FetchBidsByProtocolAndBlock(blockNumber, namespace) } -func (e *ConfidentialStoreEngine) Store(bidId BidId, sourceTx *types.Transaction, caller common.Address, key string, value []byte) (Bid, error) { +func (e *ConfidentialStoreEngine) Retrieve(bidId BidId, caller common.Address, key string) ([]byte, error) { bid, err := e.backend.FetchBidById(bidId) if err != nil { - return Bid{}, fmt.Errorf("confidential engine: could not fetch bid %x while storing: %w", bidId, err) + return []byte{}, fmt.Errorf("confidential engine: could not fetch bid %x while retrieving: %w", bidId, err) } if !slices.Contains(bid.AllowedPeekers, caller) { - return Bid{}, fmt.Errorf("confidential engine: %x not allowed to store %s on %x", caller, key, bidId) + return []byte{}, fmt.Errorf("confidential engine: %x not allowed to retrieve %s on %x", caller, key, bidId) } - msg := DAMessage{ - Bid: bid, - SourceTx: sourceTx, - Caller: caller, - Key: key, - Value: value, - StoreUUID: e.storeUUID, - } + return e.backend.Retrieve(bid, caller, key) +} - msgBytes, err := SerializeMessageForSigning(msg) - if err != nil { - return Bid{}, fmt.Errorf("confidential engine: could not hash message for signing: %w", err) +func (e *ConfidentialStoreEngine) Finalize(tx *types.Transaction, newBids map[BidId]Bid, stores []StoreWrite) error { + // + for _, bid := range newBids { + err := e.backend.InitializeBid(bid) + if err != nil { + // TODO: deinitialize! + return fmt.Errorf("confidential engine: store backend failed to initialize bid: %w", err) + } } - signingAccount, err := ExecutionNodeFromTransaction(sourceTx) - if err != nil { - return Bid{}, fmt.Errorf("confidential engine: could not recover execution node from source transaction: %w", err) + for _, sw := range stores { + if _, err := e.backend.Store(sw.Bid, sw.Caller, sw.Key, sw.Value); err != nil { + // TODO: deinitialize and deStore! + return fmt.Errorf("failed to store data: %w", err) + } } - msg.Signature, err = e.daSigner.Sign(signingAccount, msgBytes) - if err != nil { - return Bid{}, fmt.Errorf("confidential engine: could not sign message: %w", err) + // Sign and propagate the message + pwMsg := DAMessage{ + SourceTx: tx, + StoreWrites: stores, + StoreUUID: e.storeUUID, } - // TODO: avoid marshalling twice - e.transportTopic.Publish(msg) + if _, sigErr := e.chainSigner.Sender(tx); sigErr != nil { + log.Info("confidential engine: refusing to send writes based on unsigned transaction", "hash", tx.Hash().Hex(), "err", sigErr) + return ErrUnsignedFinalize + } - return e.backend.Store(bid, caller, key, value) -} + msgBytes, err := SerializeMessageForSigning(&pwMsg) + if err != nil { + return fmt.Errorf("confidential engine: could not hash message for signing: %w", err) + } -func (e *ConfidentialStoreEngine) Retrieve(bidId BidId, caller common.Address, key string) ([]byte, error) { - bid, err := e.backend.FetchBidById(bidId) + signingAccount, err := ExecutionNodeFromTransaction(tx) if err != nil { - return []byte{}, fmt.Errorf("confidential engine: could not fetch bid %x while retrieving: %w", bidId, err) + return fmt.Errorf("confidential engine: could not recover execution node from source transaction: %w", err) } - if !slices.Contains(bid.AllowedPeekers, caller) { - return []byte{}, fmt.Errorf("confidential engine: %x not allowed to retrieve %s on %x", caller, key, bidId) + pwMsg.Signature, err = e.daSigner.Sign(signingAccount, msgBytes) + if err != nil { + return fmt.Errorf("confidential engine: could not sign message: %w", err) } - return e.backend.Retrieve(bid, caller, key) + // TODO: avoid marshalling twice + go e.transportTopic.Publish(pwMsg) + + return nil } func (e *ConfidentialStoreEngine) NewMessage(message DAMessage) error { // Note the validation is a work in progress and not guaranteed to be correct! - innerBid := types.Bid{ - Id: message.Bid.Id, - Salt: message.Bid.Salt, - AllowedPeekers: message.Bid.AllowedPeekers, - AllowedStores: message.Bid.AllowedStores, - DecryptionCondition: message.Bid.DecryptionCondition, - Version: message.Bid.Version, - } - - expectedId, err := calculateBidId(innerBid) - - if err != nil { - return fmt.Errorf("confidential engine: could not calculate received bids id: %w", err) - } - - if expectedId != message.Bid.Id { - return fmt.Errorf("confidential engine: received bids id (%x) does not match the expected (%x)", message.Bid.Id, expectedId) - } - - msgBytes, err := SerializeMessageForSigning(message) + // Message-level validation + msgBytes, err := SerializeMessageForSigning(&message) if err != nil { return fmt.Errorf("confidential engine: could not hash received message: %w", err) } @@ -284,60 +254,92 @@ func (e *ConfidentialStoreEngine) NewMessage(message DAMessage) error { log.Info("Confidential engine: message is spoofing our storeUUID, processing anyway", "message", message) } - bidBytes, err := SerializeBidForSigning(message.Bid) - if err != nil { - return fmt.Errorf("confidential engine: could not hash received bid: %w", err) - } - recoveredBidSigner, err := e.daSigner.Sender(bidBytes, message.Bid.Signature) - if err != nil { - return fmt.Errorf("confidential engine: incorrect bid signature: %w", err) - } - expectedBidSigner, err := ExecutionNodeFromTransaction(message.Bid.CreationTx) + _, err = e.chainSigner.Sender(message.SourceTx) if err != nil { - return fmt.Errorf("confidential engine: could not recover signer from bid: %w", err) - } - if recoveredBidSigner != expectedBidSigner { - return fmt.Errorf("confidential engine: bid signer %x, expected %x", recoveredBidSigner, expectedBidSigner) + return fmt.Errorf("confidential engine: source tx for message is not signed properly: %w", err) } - if !slices.Contains(message.Bid.AllowedStores, recoveredMessageSigner) { - return fmt.Errorf("confidential engine: message signer %x not allowed to store on bid %x", recoveredMessageSigner, message.Bid.Id) - } + // TODO: check if message.SourceTx is valid and insert it into the mempool! - if !slices.Contains(message.Bid.AllowedPeekers, message.Caller) { - return fmt.Errorf("confidential engine: caller %x not allowed on bid %x", message.Caller, message.Bid.Id) - } + // Bid level validation - // TODO: move to types.Sender() - _, err = e.chainSigner.Sender(message.Bid.CreationTx) - if err != nil { - return fmt.Errorf("confidential engine: creation tx for bid id %x is not signed properly: %w", message.Bid.Id, err) - } + for _, sw := range message.StoreWrites { + expectedId, err := calculateBidId(types.Bid{ + Id: sw.Bid.Id, + Salt: sw.Bid.Salt, + DecryptionCondition: sw.Bid.DecryptionCondition, + AllowedPeekers: sw.Bid.AllowedPeekers, + AllowedStores: sw.Bid.AllowedStores, + Version: sw.Bid.Version, + }) + if err != nil { + return fmt.Errorf("confidential engine: could not calculate received bids id: %w", err) + } - _, err = e.chainSigner.Sender(message.SourceTx) - if err != nil { - return fmt.Errorf("confidential engine: source tx for message is not signed properly: %w", err) - } + if expectedId != sw.Bid.Id { + return fmt.Errorf("confidential engine: received bids id (%x) does not match the expected (%x)", sw.Bid.Id, expectedId) + } - err = e.backend.InitializeBid(message.Bid) - if err != nil { - if !errors.Is(err, ErrBidAlreadyPresent) { - return fmt.Errorf("unexpected error while initializing bid from transport: %w", err) + bidBytes, err := SerializeBidForSigning(&sw.Bid) + if err != nil { + return fmt.Errorf("confidential engine: could not hash received bid: %w", err) + } + recoveredBidSigner, err := e.daSigner.Sender(bidBytes, sw.Bid.Signature) + if err != nil { + return fmt.Errorf("confidential engine: incorrect bid signature: %w", err) + } + expectedBidSigner, err := ExecutionNodeFromTransaction(sw.Bid.CreationTx) + if err != nil { + return fmt.Errorf("confidential engine: could not recover signer from bid: %w", err) + } + if recoveredBidSigner != expectedBidSigner { + return fmt.Errorf("confidential engine: bid signer %x, expected %x", recoveredBidSigner, expectedBidSigner) + } + + if !slices.Contains(sw.Bid.AllowedStores, recoveredMessageSigner) { + return fmt.Errorf("confidential engine: sw signer %x not allowed to store on bid %x", recoveredMessageSigner, sw.Bid.Id) + } + + if !slices.Contains(sw.Bid.AllowedPeekers, sw.Caller) { + return fmt.Errorf("confidential engine: caller %x not allowed on bid %x", sw.Caller, sw.Bid.Id) + } + + // TODO: move to types.Sender() + _, err = e.chainSigner.Sender(sw.Bid.CreationTx) + if err != nil { + return fmt.Errorf("confidential engine: creation tx for bid id %x is not signed properly: %w", sw.Bid.Id, err) } } - _, err = e.backend.Store(message.Bid, message.Caller, message.Key, message.Value) - if err != nil { - return fmt.Errorf("unexpected error while storing: %w", err) + for _, sw := range message.StoreWrites { + err = e.backend.InitializeBid(sw.Bid) + if err != nil { + if !errors.Is(err, ErrBidAlreadyPresent) { + log.Error("confidential engine: unexpected error while initializing bid from transport: %w", err) + continue // Don't abandon! + } + } + + _, err = e.backend.Store(sw.Bid, sw.Caller, sw.Key, sw.Value) + if err != nil { + log.Error("confidential engine: unexpected error while storing: %w", err) + continue // Don't abandon! + } } return nil } -func SerializeBidForSigning(bid Bid) ([]byte, error) { - bid.Signature = []byte{} - - bidBytes, err := json.Marshal(bid) +func SerializeBidForSigning(bid *Bid) ([]byte, error) { + bidBytes, err := json.Marshal(Bid{ + Id: bid.Id, + Salt: bid.Salt, + DecryptionCondition: bid.DecryptionCondition, + AllowedPeekers: bid.AllowedPeekers, + AllowedStores: bid.AllowedStores, + Version: bid.Version, + CreationTx: bid.CreationTx, + }) if err != nil { return []byte{}, err } @@ -345,10 +347,13 @@ func SerializeBidForSigning(bid Bid) ([]byte, error) { return []byte(fmt.Sprintf("\x19Suave Signed Message:\n%d%s", len(bidBytes), string(bidBytes))), nil } -func SerializeMessageForSigning(message DAMessage) ([]byte, error) { - message.Signature = []byte{} - - msgBytes, err := json.Marshal(message) +func SerializeMessageForSigning(message *DAMessage) ([]byte, error) { + msgBytes, err := json.Marshal(DAMessage{ + SourceTx: message.SourceTx, + StoreWrites: message.StoreWrites, + StoreUUID: message.StoreUUID, + Signature: nil, + }) if err != nil { return []byte{}, err } @@ -387,5 +392,19 @@ func (MockChainSigner) Sender(tx *types.Transaction) (common.Address, error) { return common.Address{}, nil } - return *tx.To(), nil + return types.NewSuaveSigner(tx.ChainId()).Sender(tx) +} + +func ExecutionNodeFromTransaction(tx *types.Transaction) (common.Address, error) { + innerExecutedTx, ok := types.CastTxInner[*types.SuaveTransaction](tx) + if ok { + return innerExecutedTx.ExecutionNode, nil + } + + innerRequestTx, ok := types.CastTxInner[*types.ConfidentialComputeRequest](tx) + if ok { + return innerRequestTx.ExecutionNode, nil + } + + return common.Address{}, fmt.Errorf("transaction is not of confidential type") } diff --git a/suave/core/engine_test.go b/suave/core/engine_test.go index 4d4403cdce..8dedc72815 100644 --- a/suave/core/engine_test.go +++ b/suave/core/engine_test.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -62,13 +63,15 @@ func TestOwnMessageDropping(t *testing.T) { }} fakeDaSigner := FakeDASigner{localAddresses: []common.Address{{0x42}}} - engine, err := NewConfidentialStoreEngine(&fakeStore, MockTransport{}, fakeDaSigner, MockChainSigner{}) - require.NoError(t, err) + engine := NewConfidentialStoreEngine(&fakeStore, MockTransport{}, fakeDaSigner, MockChainSigner{}) - dummyCreationTx := types.NewTx(&types.ConfidentialComputeRequest{ + testKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + // testKeyAddress := crypto.PubkeyToAddress(testKey.PublicKey) + dummyCreationTx, err := types.SignTx(types.NewTx(&types.ConfidentialComputeRequest{ ExecutionNode: common.Address{0x42}, Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), - }) + }), types.NewSuaveSigner(new(big.Int)), testKey) + require.NoError(t, err) bidId, err := calculateBidId(types.Bid{ AllowedStores: []common.Address{{0x42}}, @@ -82,7 +85,7 @@ func TestOwnMessageDropping(t *testing.T) { AllowedPeekers: []common.Address{{}}, } - testBidBytes, err := SerializeBidForSigning(testBid) + testBidBytes, err := SerializeBidForSigning(&testBid) require.NoError(t, err) testBid.Signature, err = fakeDaSigner.Sign(common.Address{0x42}, testBidBytes) @@ -91,12 +94,12 @@ func TestOwnMessageDropping(t *testing.T) { *wasCalled = false daMessage := DAMessage{ - Bid: testBid, - SourceTx: dummyCreationTx, - StoreUUID: engine.storeUUID, + SourceTx: dummyCreationTx, + StoreUUID: engine.storeUUID, + StoreWrites: []StoreWrite{{Bid: testBid}}, } - daMessageBytes, err := SerializeMessageForSigning(daMessage) + daMessageBytes, err := SerializeMessageForSigning(&daMessage) require.NoError(t, err) daMessage.Signature, err = fakeDaSigner.Sign(common.Address{0x42}, daMessageBytes) @@ -108,7 +111,7 @@ func TestOwnMessageDropping(t *testing.T) { // require.True(t, *wasCalled) daMessage.StoreUUID = uuid.New() - daMessageBytes, err = SerializeMessageForSigning(daMessage) + daMessageBytes, err = SerializeMessageForSigning(&daMessage) require.NoError(t, err) daMessage.Signature, err = fakeDaSigner.Sign(common.Address{0x42}, daMessageBytes) diff --git a/suave/core/transactional_store.go b/suave/core/transactional_store.go new file mode 100644 index 0000000000..1709c77f31 --- /dev/null +++ b/suave/core/transactional_store.go @@ -0,0 +1,113 @@ +package suave + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "golang.org/x/exp/slices" +) + +type TransactionalStore struct { + sourceTx *types.Transaction + engine *ConfidentialStoreEngine + + pendingLock sync.Mutex + pendingBids map[BidId]Bid + pendingWrites []StoreWrite +} + +func (s *TransactionalStore) FetchBidById(bidId BidId) (Bid, error) { + s.pendingLock.Lock() + bid, ok := s.pendingBids[bidId] + s.pendingLock.Unlock() + + if ok { + return bid, nil + } + + return s.engine.FetchBidById(bidId) +} + +func (s *TransactionalStore) FetchBidsByProtocolAndBlock(blockNumber uint64, namespace string) []Bid { + bids := s.engine.FetchBidsByProtocolAndBlock(blockNumber, namespace) + + s.pendingLock.Lock() + defer s.pendingLock.Unlock() + for _, bid := range s.pendingBids { + if bid.Version == namespace && bid.DecryptionCondition == blockNumber { + bids = append(bids, bid) + } + } + + return bids +} + +func (s *TransactionalStore) Store(bidId BidId, caller common.Address, key string, value []byte) (Bid, error) { + bid, err := s.FetchBidById(bidId) + if err != nil { + return Bid{}, err + } + + if !slices.Contains(bid.AllowedPeekers, caller) { + return Bid{}, fmt.Errorf("confidential store transaction: %x not allowed to store %s on %x", caller, key, bidId) + } + + s.pendingLock.Lock() + defer s.pendingLock.Unlock() + s.pendingWrites = append(s.pendingWrites, StoreWrite{ + Bid: bid, + Caller: caller, + Key: key, + Value: common.CopyBytes(value), + }) + + return bid, nil +} + +func (s *TransactionalStore) Retrieve(bidId BidId, caller common.Address, key string) ([]byte, error) { + bid, err := s.FetchBidById(bidId) + if err != nil { + return nil, err + } + + if !slices.Contains(bid.AllowedPeekers, caller) { + return nil, fmt.Errorf("confidential store transaction: %x not allowed to retrieve %s on %x", caller, key, bidId) + } + + s.pendingLock.Lock() + + for _, sw := range s.pendingWrites { + if sw.Bid.Id == bid.Id && sw.Key == key { + s.pendingLock.Unlock() + return common.CopyBytes(sw.Value), nil + } + } + + s.pendingLock.Unlock() + return s.engine.Retrieve(bidId, caller, key) +} + +func (s *TransactionalStore) InitializeBid(rawBid types.Bid) (types.Bid, error) { + bid, err := s.engine.InitializeBid(rawBid, s.sourceTx) + if err != nil { + return types.Bid{}, err + } + + s.pendingLock.Lock() + _, found := s.pendingBids[bid.Id] + if found { + s.pendingLock.Unlock() + return types.Bid{}, errors.New("bid with this id already exists") + } + s.pendingBids[bid.Id] = bid + s.pendingLock.Unlock() + + return bid.ToInnerBid(), nil +} + +func (s *TransactionalStore) Finalize() error { + return s.engine.Finalize(s.sourceTx, s.pendingBids, s.pendingWrites) +} diff --git a/suave/core/types.go b/suave/core/types.go index b222016bd4..89c5d9e238 100644 --- a/suave/core/types.go +++ b/suave/core/types.go @@ -43,7 +43,20 @@ type BuildBlockArgs = types.BuildBlockArgs var ConfStoreAllowedAny common.Address = common.HexToAddress("0x42") -var ErrBidAlreadyPresent = errors.New("bid already present") +var ( + ErrBidAlreadyPresent = errors.New("bid already present") + ErrUnsignedFinalize = errors.New("finalize called with unsigned transaction, refusing to propagate") +) + +type DASigner interface { + Sign(account common.Address, data []byte) ([]byte, error) + Sender(data []byte, signature []byte) (common.Address, error) + LocalAddresses() []common.Address +} + +type ChainSigner interface { + Sender(tx *types.Transaction) (common.Address, error) +} type ConfidentialStoreBackend interface { node.Lifecycle @@ -67,11 +80,15 @@ type StoreTransportTopic interface { } type DAMessage struct { - Bid Bid `json:"bid"` - SourceTx *types.Transaction `json:"sourceTx"` - Caller common.Address `json:"caller"` - Key string `json:"key"` - Value Bytes `json:"value"` - StoreUUID uuid.UUID `json:"storeUUID"` - Signature Bytes `json:"signature"` + SourceTx *types.Transaction `json:"sourceTx"` + StoreWrites []StoreWrite `json:"storeWrites"` + StoreUUID uuid.UUID `json:"storeUUID"` + Signature Bytes `json:"signature"` +} + +type StoreWrite struct { + Bid Bid `json:"bid"` + Caller common.Address `json:"caller"` + Key string `json:"key"` + Value Bytes `json:"value"` } diff --git a/suave/e2e/workflow_test.go b/suave/e2e/workflow_test.go index 4640477c8d..813b9bd2c4 100644 --- a/suave/e2e/workflow_test.go +++ b/suave/e2e/workflow_test.go @@ -136,23 +136,27 @@ func TestMempool(t *testing.T) { { targetBlock := uint64(16103213) + creationTx := types.NewTx(&types.ConfidentialComputeRequest{ExecutionNode: fr.ExecutionNode(), Wrapped: *types.NewTx(&types.LegacyTx{})}) - bid1 := suave.Bid{ - Id: suave.RandomBidId(), + bid1, err := fr.ConfidentialEngine().InitializeBid(types.Bid{ + Salt: suave.RandomBidId(), DecryptionCondition: targetBlock, AllowedPeekers: []common.Address{common.HexToAddress("0x424344")}, Version: "default:v0:ethBundles", - } + }, creationTx) + + require.NoError(t, err) - bid2 := suave.Bid{ - Id: suave.RandomBidId(), + bid2, err := fr.ConfidentialEngine().InitializeBid(types.Bid{ + Salt: suave.RandomBidId(), DecryptionCondition: targetBlock, AllowedPeekers: []common.Address{common.HexToAddress("0x424344")}, Version: "default:v0:ethBundles", - } + }, creationTx) + require.NoError(t, err) - require.NoError(t, fr.ConfidentialStore().StoreBid(bid1)) - require.NoError(t, fr.ConfidentialStore().StoreBid(bid2)) + require.NoError(t, fr.ConfidentialStoreBackend().InitializeBid(bid1)) + require.NoError(t, fr.ConfidentialStoreBackend().InitializeBid(bid2)) inoutAbi := mustParseMethodAbi(`[ { "inputs": [ { "internalType": "uint64", "name": "cond", "type": "uint64" }, { "internalType": "string", "name": "namespace", "type": "string" } ], "name": "fetchBids", "outputs": [ { "components": [ { "internalType": "Suave.BidId", "name": "id", "type": "bytes16" }, { "internalType": "Suave.BidId", "name": "salt", "type": "bytes16" }, { "internalType": "uint64", "name": "decryptionCondition", "type": "uint64" }, { "internalType": "address[]", "name": "allowedPeekers", "type": "address[]" }, { "internalType": "address[]", "name": "allowedStores", "type": "address[]" }, { "internalType": "string", "name": "version", "type": "string" } ], "internalType": "struct Suave.Bid[]", "name": "", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" } ]`, "fetchBids") @@ -174,18 +178,17 @@ func TestMempool(t *testing.T) { var bids []suave.Bid require.NoError(t, mapstructure.Decode(unpacked[0], &bids)) - require.Equal(t, bid1, suave.Bid{ - Id: bids[0].Id, - DecryptionCondition: bids[0].DecryptionCondition, - AllowedPeekers: bids[0].AllowedPeekers, - Version: bids[0].Version, - }) - require.Equal(t, bid2, suave.Bid{ - Id: bids[1].Id, - DecryptionCondition: bids[1].DecryptionCondition, - AllowedPeekers: bids[1].AllowedPeekers, - Version: bids[1].Version, - }) + require.Equal(t, bid1.Id, bids[0].Id) + require.Equal(t, bid1.Salt, bids[0].Salt) + require.Equal(t, bid1.DecryptionCondition, bids[0].DecryptionCondition) + require.Equal(t, bid1.AllowedPeekers, bids[0].AllowedPeekers) + require.Equal(t, bid1.Version, bids[0].Version) + + require.Equal(t, bid2.Id, bids[1].Id) + require.Equal(t, bid2.Salt, bids[1].Salt) + require.Equal(t, bid2.DecryptionCondition, bids[1].DecryptionCondition) + require.Equal(t, bid2.AllowedPeekers, bids[1].AllowedPeekers) + require.Equal(t, bid2.Version, bids[1].Version) // Verify via transaction wrappedTxData := &types.LegacyTx{ @@ -285,7 +288,7 @@ func TestBundleBid(t *testing.T) { require.Equal(t, bid.DecryptionCondition, unpacked[1].(uint64)) require.Equal(t, bid.AllowedPeekers, unpacked[2].([]common.Address)) - _, err = fr.ConfidentialStore().Retrieve(bid.Id, common.Address{0x41, 0x42, 0x43}, "default:v0:ethBundleSimResults") + _, err = fr.ConfidentialEngine().Retrieve(bid.Id, common.Address{0x41, 0x42, 0x43}, "default:v0:ethBundleSimResults") require.NoError(t, err) } } @@ -432,7 +435,7 @@ func TestMevShare(t *testing.T) { require.NoError(t, err) bidId := unpacked[0].([16]byte) - payloadData, err := fr.ConfidentialStore().Retrieve(bidId, newBlockBidAddress, "default:v0:builderPayload") + payloadData, err := fr.ConfidentialEngine().Retrieve(bidId, newBlockBidAddress, "default:v0:builderPayload") require.NoError(t, err) var payloadEnvelope engine.ExecutionPayloadEnvelope @@ -504,12 +507,13 @@ func TestBlockBuildingPrecompiles(t *testing.T) { { // Test the block building precompile through eth_call // function buildEthBlock(BuildBlockArgs memory blockArgs, BidId bid) internal view returns (bytes memory, bytes memory) { - dummyCreationTx := types.NewTx(&types.ConfidentialComputeRequest{ + dummyCreationTx, err := types.SignNewTx(testKey, signer, &types.ConfidentialComputeRequest{ ExecutionNode: fr.ExecutionNode(), - Wrapped: *types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), + Wrapped: *types.NewTx(&types.LegacyTx{}), }) + require.NoError(t, err) - bid, err := fr.ConfidentialStore().InitializeBid(types.Bid{ + bid, err := fr.ConfidentialEngine().InitializeBid(types.Bid{ DecryptionCondition: uint64(1), AllowedPeekers: []common.Address{{0x41, 0x42, 0x43}, buildEthBlockAddress}, AllowedStores: []common.Address{fr.ExecutionNode()}, @@ -517,7 +521,13 @@ func TestBlockBuildingPrecompiles(t *testing.T) { }, dummyCreationTx) require.NoError(t, err) - _, err = fr.ConfidentialStore().Store(bid.Id, dummyCreationTx, common.Address{0x41, 0x42, 0x43}, "default:v0:ethBundles", bundleBytes) + err = fr.ConfidentialEngine().Finalize(dummyCreationTx, map[suave.BidId]suave.Bid{bid.Id: bid}, []suave.StoreWrite{{ + + Bid: bid, + Caller: common.Address{0x41, 0x42, 0x43}, + Key: "default:v0:ethBundles", + Value: bundleBytes, + }}) require.NoError(t, err) ethHead := fr.ethSrv.CurrentBlock() @@ -802,8 +812,9 @@ type frameworkConfig struct { } var defaultFrameworkConfig = frameworkConfig{ - executionNode: false, - suaveConfig: suave.Config{}, + executionNode: false, + redisStoreBackend: false, + suaveConfig: suave.Config{}, } type frameworkOpt func(*frameworkConfig) @@ -864,8 +875,12 @@ func (f *framework) NewSDKClient() *sdk.Client { return sdk.NewClient(f.suethSrv.RPCNode(), testKey, f.ExecutionNode()) } -func (f *framework) ConfidentialStore() *suave.ConfidentialStoreEngine { - return f.suethSrv.service.ConfidentialStore +func (f *framework) ConfidentialStoreBackend() suave.ConfidentialStoreBackend { + return f.suethSrv.service.APIBackend.SuaveEngine().Backend() +} + +func (f *framework) ConfidentialEngine() *suave.ConfidentialStoreEngine { + return f.suethSrv.service.APIBackend.SuaveEngine() } func (f *framework) ExecutionNode() common.Address {