diff --git a/core/rawdb/accessors_row_consumption_test.go b/core/rawdb/accessors_row_consumption_test.go index 4195b3d2b4..0583f6425e 100644 --- a/core/rawdb/accessors_row_consumption_test.go +++ b/core/rawdb/accessors_row_consumption_test.go @@ -2,6 +2,7 @@ package rawdb import ( "math/big" + "reflect" "testing" "github.com/scroll-tech/go-ethereum/common" @@ -10,13 +11,11 @@ import ( func TestReadBlockRowConsumption(t *testing.T) { l2BlockHash := common.BigToHash(big.NewInt(10)) - rc := types.RowConsumption{ - Rows: 11, - } + rc := types.RowConsumption{types.SubCircuitRowConsumption{CircuitName: "aa", Rows: 12}, types.SubCircuitRowConsumption{CircuitName: "bb", Rows: 100}} db := NewMemoryDatabase() WriteBlockRowConsumption(db, l2BlockHash, rc) got := ReadBlockRowConsumption(db, l2BlockHash) - if got == nil || got.Rows != rc.Rows { + if got == nil || !reflect.DeepEqual(rc, *got) { t.Fatal("RowConsumption mismatch", "expected", rc, "got", got) } } diff --git a/core/types/block.go b/core/types/block.go index f3e7c83790..32176dfe16 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -418,3 +418,8 @@ func (b *Block) CountL2Tx() int { } type Blocks []*Block + +type BlockWithRowConsumption struct { + *Block + *RowConsumption +} diff --git a/core/types/gen_row_consumption_json.go b/core/types/gen_row_consumption_json.go new file mode 100644 index 0000000000..2b97a016dc --- /dev/null +++ b/core/types/gen_row_consumption_json.go @@ -0,0 +1,45 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/scroll-tech/go-ethereum/common/hexutil" +) + +var _ = (*subCircuitRowConsumptionMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s SubCircuitRowConsumption) MarshalJSON() ([]byte, error) { + type SubCircuitRowConsumption struct { + CircuitName string `json:"circuitName" gencodec:"required"` + Rows hexutil.Uint64 `json:"rows" gencodec:"required"` + } + var enc SubCircuitRowConsumption + enc.CircuitName = s.CircuitName + enc.Rows = hexutil.Uint64(s.Rows) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *SubCircuitRowConsumption) UnmarshalJSON(input []byte) error { + type SubCircuitRowConsumption struct { + CircuitName *string `json:"circuitName" gencodec:"required"` + Rows *hexutil.Uint64 `json:"rows" gencodec:"required"` + } + var dec SubCircuitRowConsumption + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.CircuitName == nil { + return errors.New("missing required field 'circuitName' for SubCircuitRowConsumption") + } + s.CircuitName = *dec.CircuitName + if dec.Rows == nil { + return errors.New("missing required field 'rows' for SubCircuitRowConsumption") + } + s.Rows = uint64(*dec.Rows) + return nil +} diff --git a/core/types/row_consumption.go b/core/types/row_consumption.go index dbd2cd0852..380406f48a 100644 --- a/core/types/row_consumption.go +++ b/core/types/row_consumption.go @@ -1,28 +1,18 @@ package types import ( - "fmt" - "io" - - "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/common/hexutil" ) -type RowConsumption struct { - Rows uint64 +//go:generate gencodec -type SubCircuitRowConsumption -field-override subCircuitRowConsumptionMarshaling -out gen_row_consumption_json.go +type SubCircuitRowConsumption struct { + CircuitName string `json:"circuitName" gencodec:"required"` + Rows uint64 `json:"rows" gencodec:"required"` } -func (rc *RowConsumption) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, rc.Rows) -} +type RowConsumption []SubCircuitRowConsumption -func (rc *RowConsumption) DecodeRLP(s *rlp.Stream) error { - _, size, err := s.Kind() - if err != nil { - return err - } - if size <= 8 { - return s.Decode(&rc.Rows) - } else { - return fmt.Errorf("invalid input size %d for origin", size) - } +// field type overrides for gencodec +type subCircuitRowConsumptionMarshaling struct { + Rows hexutil.Uint64 } diff --git a/eth/api.go b/eth/api.go index e855cf019c..d1c9fee90a 100644 --- a/eth/api.go +++ b/eth/api.go @@ -682,7 +682,7 @@ func (api *ScrollAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, fullT fields["totalDifficulty"] = (*hexutil.Big)(api.eth.APIBackend.GetTd(ctx, b.Hash())) rc := rawdb.ReadBlockRowConsumption(api.eth.ChainDb(), b.Hash()) if rc != nil { - fields["rowConsumption"] = hexutil.Uint64(rc.Rows) + fields["rowConsumption"] = rc } else { fields["rowConsumption"] = nil } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index bded33ef72..5f1f54794f 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -337,12 +337,104 @@ func (ec *Client) GetBlockTraceByNumber(ctx context.Context, number *big.Int) (* return blockTrace, ec.c.CallContext(ctx, &blockTrace, "scroll_getBlockTraceByNumberOrHash", toBlockNumArg(number)) } +type rpcRowConsumption struct { + RowConsumption types.RowConsumption `json:"rowConsumption"` +} + +// UnmarshalJSON unmarshals from JSON. +func (r *rpcRowConsumption) UnmarshalJSON(input []byte) error { + type rpcRowConsumption struct { + RowConsumption types.RowConsumption `json:"rowConsumption"` + } + var dec rpcRowConsumption + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.RowConsumption == nil { + return errors.New("missing required field 'RowConsumption' for rpcRowConsumption") + } + r.RowConsumption = dec.RowConsumption + return nil +} + // GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full // detail, otherwise only the transaction hash is returned. -func (ec *Client) GetBlockByHash(ctx context.Context, blockHash common.Hash, inclTx bool) (map[string]interface{}, error) { - block := make(map[string]interface{}) - err := ec.c.CallContext(ctx, &block, "scroll_getBlockByHash", blockHash, inclTx) - return block, err +func (ec *Client) GetBlockByHash(ctx context.Context, blockHash common.Hash) (*types.BlockWithRowConsumption, error) { + var raw json.RawMessage + err := ec.c.CallContext(ctx, &raw, "scroll_getBlockByHash", blockHash, true) + if err != nil { + return nil, err + } else if len(raw) == 0 { + return nil, ethereum.NotFound + } + // Decode header and transactions. + var head *types.Header + var body rpcBlock + var rpcRc rpcRowConsumption + var rc *types.RowConsumption + if err := json.Unmarshal(raw, &head); err != nil { + return nil, err + } + if err := json.Unmarshal(raw, &body); err != nil { + return nil, err + } + if err := json.Unmarshal(raw, &rpcRc); err != nil { + // don't return error here if there is no RowConsumption data, because many l2geth nodes will not have this data + // instead of error l2_watcher and other services that require RowConsumption should check it + rc = nil + } else { + rc = &rpcRc.RowConsumption + } + // Quick-verify transaction and uncle lists. This mostly helps with debugging the server. + if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 { + return nil, fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles") + } + if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 { + return nil, fmt.Errorf("server returned empty uncle list but block header indicates uncles") + } + if head.TxHash == types.EmptyRootHash && len(body.Transactions) > 0 { + return nil, fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions") + } + if head.TxHash != types.EmptyRootHash && len(body.Transactions) == 0 { + return nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions") + } + // Load uncles because they are not included in the block response. + var uncles []*types.Header + if len(body.UncleHashes) > 0 { + uncles = make([]*types.Header, len(body.UncleHashes)) + reqs := make([]rpc.BatchElem, len(body.UncleHashes)) + for i := range reqs { + reqs[i] = rpc.BatchElem{ + Method: "eth_getUncleByBlockHashAndIndex", + Args: []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))}, + Result: &uncles[i], + } + } + if err := ec.c.BatchCallContext(ctx, reqs); err != nil { + return nil, err + } + for i := range reqs { + if reqs[i].Error != nil { + return nil, reqs[i].Error + } + if uncles[i] == nil { + return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:]) + } + } + } + // Fill the sender cache of transactions in the block. + txs := make([]*types.Transaction, len(body.Transactions)) + for i, tx := range body.Transactions { + if tx.From != nil { + setSenderFromServer(tx.tx, *tx.From, body.Hash) + } + txs[i] = tx.tx + } + block := types.NewBlockWithHeader(head).WithBody(txs, uncles) + return &types.BlockWithRowConsumption{ + Block: block, + RowConsumption: rc, + }, nil } // SubscribeNewBlockTrace subscribes to block execution trace when a new block is created. diff --git a/params/version.go b/params/version.go index 249133d886..ddac51b152 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 10 // Patch version component of the current release + VersionPatch = 11 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string )