diff --git a/builder/builder.go b/builder/builder.go index 29f10fd4..83e973dd 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -85,16 +85,17 @@ func (b *Builder) Rollback(head, safe, finalized common.Hash) error { } type Payload struct { - // Transactions functions as an inclusion list. - Transactions bfttypes.Txs + // InjectedTransactions functions as an inclusion list. It contains transactions + // from the consensus layer that must be included in the block. + InjectedTransactions bfttypes.Txs // TODO: make the gas limit actually be enforced. Need to translate between cosmos and op gas limit. GasLimit uint64 Timestamp uint64 NoTxPool bool } -func (b *Builder) Build(payload *Payload) error { - txs := slices.Clone(payload.Transactions) // Shallow clone is ok, we just don't want to modify the slice itself. +func (b *Builder) Build(payload *Payload) (*monomer.Block, error) { + txs := slices.Clone(payload.InjectedTransactions) // Shallow clone is ok, we just don't want to modify the slice itself. if !payload.NoTxPool { for { // TODO there is risk of losing txs if mempool db fails. @@ -120,7 +121,7 @@ func (b *Builder) Build(payload *Payload) error { currentHeight := info.GetLastBlockHeight() currentHead := b.blockStore.BlockByNumber(currentHeight) if currentHead == nil { - return fmt.Errorf("block not found at height: %d", currentHeight) + return nil, fmt.Errorf("block not found at height: %d", currentHeight) } header := &monomer.Header{ ChainID: b.chainID, @@ -141,7 +142,7 @@ func (b *Builder) Build(payload *Payload) error { Tx: tx, }) if resp.IsErr() { - return fmt.Errorf("deliver tx: %v", resp.GetLog()) + return nil, fmt.Errorf("deliver tx: %v", resp.GetLog()) } txResults = append(txResults, &abcitypes.TxResult{ Height: currentHeight + 1, @@ -155,23 +156,26 @@ func (b *Builder) Build(payload *Payload) error { }) b.app.Commit() - // Append block. - b.blockStore.AddBlock(&monomer.Block{ + block := &monomer.Block{ Header: header, Txs: txs, - }) + } + + // Append block. + b.blockStore.AddBlock(block) // Index txs. if err := b.txStore.Add(txResults); err != nil { - return fmt.Errorf("add tx results: %v", err) + return nil, fmt.Errorf("add tx results: %v", err) } // Publish events. for _, txResult := range txResults { if err := b.eventBus.PublishEventTx(bfttypes.EventDataTx{ TxResult: *txResult, }); err != nil { - return fmt.Errorf("publish event tx: %v", err) + return nil, fmt.Errorf("publish event tx: %v", err) } } + // TODO publish other things like new blocks. - return nil + return block, nil } diff --git a/builder/builder_test.go b/builder/builder_test.go index 7eb4e0ec..b0bb9e9e 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -114,13 +114,14 @@ func TestBuild(t *testing.T) { ) payload := &builder.Payload{ - Transactions: bfttypes.ToTxs(inclusionListTxs), - GasLimit: 0, - Timestamp: g.Time + 1, - NoTxPool: test.noTxPool, + InjectedTransactions: bfttypes.ToTxs(inclusionListTxs), + GasLimit: 0, + Timestamp: g.Time + 1, + NoTxPool: test.noTxPool, } preBuildInfo := app.Info(abcitypes.RequestInfo{}) - require.NoError(t, b.Build(payload)) + builtBlock, err := b.Build(payload) + require.NoError(t, err) postBuildInfo := app.Info(abcitypes.RequestInfo{}) // Application. @@ -154,6 +155,7 @@ func TestBuild(t *testing.T) { Txs: bfttypes.ToTxs(allTxs), } wantBlock.Hash() + require.Equal(t, wantBlock, builtBlock) require.Equal(t, wantBlock, gotBlock) // Tx store and event bus. @@ -216,11 +218,11 @@ func TestRollback(t *testing.T) { kvs := map[string]string{ "test": "test", } - require.NoError(t, b.Build(&builder.Payload{ - Timestamp: g.Time + 1, - Transactions: bfttypes.ToTxs(testapp.ToTxs(t, kvs)), - })) - block := blockStore.HeadBlock() + block, err := b.Build(&builder.Payload{ + Timestamp: g.Time + 1, + InjectedTransactions: bfttypes.ToTxs(testapp.ToTxs(t, kvs)), + }) + require.NoError(t, err) require.NotNil(t, block) require.NoError(t, blockStore.UpdateLabel(eth.Unsafe, block.Hash())) require.NoError(t, blockStore.UpdateLabel(eth.Safe, block.Hash())) diff --git a/engine/engine.go b/engine/engine.go index 0dd46590..a43d45b5 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/polymerdao/monomer" "github.com/polymerdao/monomer/app/peptide/store" "github.com/polymerdao/monomer/builder" @@ -26,7 +27,8 @@ type EngineAPI struct { txValidator TxValidator blockStore BlockStore currentPayloadAttributes *monomer.PayloadAttributes - adapter monomer.PayloadTxAdapter + ethCosmosAdapter monomer.PayloadTxAdapter + cosmosEthAdapter monomer.CosmosTxAdapter lock sync.RWMutex } @@ -34,12 +36,19 @@ type TxValidator interface { CheckTx(abci.RequestCheckTx) abci.ResponseCheckTx } -func NewEngineAPI(b *builder.Builder, txValidator TxValidator, adapter monomer.PayloadTxAdapter, blockStore BlockStore) *EngineAPI { +func NewEngineAPI( + b *builder.Builder, + txValidator TxValidator, + ethCosmosAdapter monomer.PayloadTxAdapter, + cosmosEthAdapter monomer.CosmosTxAdapter, + blockStore BlockStore, +) *EngineAPI { return &EngineAPI{ - txValidator: txValidator, - blockStore: blockStore, - builder: b, - adapter: adapter, + txValidator: txValidator, + blockStore: blockStore, + builder: b, + ethCosmosAdapter: ethCosmosAdapter, + cosmosEthAdapter: cosmosEthAdapter, } } @@ -159,7 +168,7 @@ func (e *EngineAPI) ForkchoiceUpdatedV3( return nil, engine.InvalidPayloadAttributes.With(errors.New("gas limit not provided")) } - cosmosTxs, err := e.adapter(pa.Transactions) + cosmosTxs, err := e.ethCosmosAdapter(pa.Transactions) if err != nil { return nil, engine.InvalidPayloadAttributes.With(fmt.Errorf("convert payload attributes txs to cosmos txs: %v", err)) } @@ -237,16 +246,42 @@ func (e *EngineAPI) GetPayloadV3(payloadID engine.PayloadID) (*eth.ExecutionPayl // TODO: handle time slot based block production // for now assume block is sealed by this call - if err := e.builder.Build(&builder.Payload{ - Transactions: e.currentPayloadAttributes.CosmosTxs, - GasLimit: e.currentPayloadAttributes.GasLimit, - Timestamp: e.currentPayloadAttributes.Timestamp, - NoTxPool: e.currentPayloadAttributes.NoTxPool, - }); err != nil { + block, err := e.builder.Build(&builder.Payload{ + InjectedTransactions: e.currentPayloadAttributes.CosmosTxs, + GasLimit: e.currentPayloadAttributes.GasLimit, + Timestamp: e.currentPayloadAttributes.Timestamp, + NoTxPool: e.currentPayloadAttributes.NoTxPool, + }) + if err != nil { log.Panicf("failed to commit block: %v", err) // TODO error handling. An error here is potentially a big problem. } - payloadEnvelope := e.currentPayloadAttributes.ToExecutionPayloadEnvelope(e.blockStore.HeadBlock().Hash()) + txs, err := e.cosmosEthAdapter(block.Txs) + if err != nil { + return nil, engine.GenericServerError.With(fmt.Errorf("convert cosmos txs to eth txs: %v", err)) + } + + txBytes := make([]hexutil.Bytes, len(txs)) + for i, tx := range txs { + txBytes[i], err = tx.MarshalBinary() + if err != nil { + return nil, engine.GenericServerError.With(fmt.Errorf("marshal tx binary: %v", err)) + } + } + + payloadEnvelope := ð.ExecutionPayloadEnvelope{ + ExecutionPayload: ð.ExecutionPayload{ + ParentHash: e.currentPayloadAttributes.ParentHash, + BlockNumber: hexutil.Uint64(e.currentPayloadAttributes.Height), + BlockHash: block.Hash(), + FeeRecipient: e.currentPayloadAttributes.SuggestedFeeRecipient, + Timestamp: hexutil.Uint64(e.currentPayloadAttributes.Timestamp), + PrevRandao: e.currentPayloadAttributes.PrevRandao, + Withdrawals: e.currentPayloadAttributes.Withdrawals, + Transactions: txBytes, + GasLimit: hexutil.Uint64(e.currentPayloadAttributes.GasLimit), + }, + } // remove payload e.currentPayloadAttributes = nil diff --git a/monomer.go b/monomer.go index aec2f8f2..cc97adb2 100644 --- a/monomer.go +++ b/monomer.go @@ -212,22 +212,6 @@ func hashDataAsBinary(h hash.Hash, data any) { } } -func (p *PayloadAttributes) ToExecutionPayloadEnvelope(blockHash common.Hash) *opeth.ExecutionPayloadEnvelope { - return &opeth.ExecutionPayloadEnvelope{ - ExecutionPayload: &opeth.ExecutionPayload{ - ParentHash: p.ParentHash, - BlockNumber: hexutil.Uint64(p.Height), - BlockHash: blockHash, - FeeRecipient: p.SuggestedFeeRecipient, - Timestamp: hexutil.Uint64(p.Timestamp), - PrevRandao: p.PrevRandao, - Withdrawals: p.Withdrawals, - Transactions: p.Transactions, - GasLimit: hexutil.Uint64(p.GasLimit), - }, - } -} - // ValidForkchoiceUpdateResult returns a valid ForkchoiceUpdateResult with given head block hash. func ValidForkchoiceUpdateResult(headBlockHash *common.Hash, id *engine.PayloadID) *opeth.ForkchoiceUpdatedResult { return &opeth.ForkchoiceUpdatedResult{ diff --git a/node/node.go b/node/node.go index b94a7f91..fa93ef54 100644 --- a/node/node.go +++ b/node/node.go @@ -97,6 +97,7 @@ func (n *Node) Run(ctx context.Context, env *environment.Env) error { builder.New(mpool, n.app, blockStore, txStore, eventBus, n.genesis.ChainID), n.app, n.adaptPayloadTxsToCosmosTxs, + n.adaptCosmosTxsToEthTxs, blockStore, ), },