From 5db0c7b09e780fe1a706a7f663a3fbc532e24316 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 12 Sep 2024 17:47:37 +0200 Subject: [PATCH] all: implement flat deposit requests encoding --- beacon/engine/gen_ed.go | 18 ++- beacon/engine/types.go | 37 ++---- cmd/evm/internal/t8ntool/execution.go | 13 +- core/block_validator.go | 6 +- core/blockchain_test.go | 173 +++++++++++++------------- core/chain_makers.go | 6 +- core/genesis.go | 4 +- core/state_processor.go | 10 +- core/types.go | 2 +- core/types/block.go | 19 ++- core/types/deposit.go | 81 ++++-------- core/types/gen_deposit_json.go | 70 ----------- core/types/hashes.go | 2 +- core/types/request.go | 157 ----------------------- eth/catalyst/api.go | 41 +++--- eth/catalyst/api_test.go | 13 +- eth/downloader/queue.go | 2 +- eth/protocols/eth/handlers.go | 2 +- eth/protocols/eth/protocol.go | 6 +- ethclient/ethclient.go | 14 ++- 20 files changed, 198 insertions(+), 478 deletions(-) delete mode 100644 core/types/gen_deposit_json.go delete mode 100644 core/types/request.go diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index b2eb1dc982..2494bfb1f9 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -34,7 +34,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - Deposits types.Deposits `json:"depositRequests"` + Requests []hexutil.Bytes `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } var enc ExecutableData @@ -60,7 +60,12 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.Withdrawals = e.Withdrawals enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) - enc.Deposits = e.Deposits + if e.Requests != nil { + enc.Requests = make([]hexutil.Bytes, len(e.Requests)) + for k, v := range e.Requests { + enc.Requests[k] = v + } + } enc.ExecutionWitness = e.ExecutionWitness return json.Marshal(&enc) } @@ -85,7 +90,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - Deposits *types.Deposits `json:"depositRequests"` + Requests []hexutil.Bytes `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } var dec ExecutableData @@ -160,8 +165,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.ExcessBlobGas != nil { e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) } - if dec.Deposits != nil { - e.Deposits = *dec.Deposits + if dec.Requests != nil { + e.Requests = make([][]byte, len(dec.Requests)) + for k, v := range dec.Requests { + e.Requests[k] = v + } } if dec.ExecutionWitness != nil { e.ExecutionWitness = dec.ExecutionWitness diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 74c56f403d..1d0cfd7364 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -76,7 +76,7 @@ type ExecutableData struct { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *uint64 `json:"blobGasUsed"` ExcessBlobGas *uint64 `json:"excessBlobGas"` - Deposits types.Deposits `json:"depositRequests"` + Requests [][]byte `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } @@ -91,6 +91,7 @@ type executableDataMarshaling struct { LogsBloom hexutil.Bytes Transactions []hexutil.Bytes BlobGasUsed *hexutil.Uint64 + Requests []hexutil.Bytes ExcessBlobGas *hexutil.Uint64 } @@ -257,16 +258,9 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H withdrawalsRoot = &h } // Compute requestsHash if any requests are non-nil. - var ( - requestsHash *common.Hash - requests types.Requests - ) - if data.Deposits != nil { - requests = make(types.Requests, 0) - for _, d := range data.Deposits { - requests = append(requests, types.NewRequest(d)) - } - h := types.DeriveSha(requests, trie.NewStackTrie(nil)) + var requestsHash *common.Hash + if data.Requests != nil { + h := types.CalcRequestHash(data.Requests) requestsHash = &h } header := &types.Header{ @@ -292,7 +286,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H RequestsHash: requestsHash, } return types.NewBlockWithHeader(header). - WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests}). + WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: data.Requests}). WithWitness(data.ExecutionWitness), nil } @@ -318,6 +312,7 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. Withdrawals: block.Withdrawals(), BlobGasUsed: block.BlobGasUsed(), ExcessBlobGas: block.ExcessBlobGas(), + Requests: block.Requests(), ExecutionWitness: block.ExecutionWitness(), } bundle := BlobsBundleV1{ @@ -332,30 +327,14 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) } } - setRequests(block.Requests(), data) return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false} } -// setRequests differentiates the different request types and -// assigns them to the associated fields in ExecutableData. -func setRequests(requests types.Requests, data *ExecutableData) { - if requests != nil { - // If requests is non-nil, it means deposits are available in block and we - // should return an empty slice instead of nil if there are no deposits. - data.Deposits = make(types.Deposits, 0) - } - for _, r := range requests { - if d, ok := r.Inner().(*types.Deposit); ok { - data.Deposits = append(data.Deposits, d) - } - } -} - // ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange type ExecutionPayloadBody struct { TransactionData []hexutil.Bytes `json:"transactions"` Withdrawals []*types.Withdrawal `json:"withdrawals"` - Deposits types.Deposits `json:"depositRequests"` + Requests []hexutil.Bytes `json:"requests"` } // Client identifiers to support ClientVersionV1. diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 5fd1d6a4a6..63cd0f8036 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -67,7 +67,7 @@ type ExecutionResult struct { CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` RequestsHash *common.Hash `json:"requestsRoot,omitempty"` - DepositRequests *types.Deposits `json:"depositRequests,omitempty"` + Requests [][]byte `json:"requests,omitempty"` } type ommer struct { @@ -390,16 +390,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err)) } // Calculate the requests root - h := types.DeriveSha(requests, trie.NewStackTrie(nil)) + h := types.CalcRequestHash(requests) execRs.RequestsHash = &h - // Get the deposits from the requests - deposits := make(types.Deposits, 0) - for _, req := range requests { - if dep, ok := req.Inner().(*types.Deposit); ok { - deposits = append(deposits, dep) - } - } - execRs.DepositRequests = &deposits + execRs.Requests = requests } // Re-create statedb instance with new root upon the updated database // for accessing latest states. diff --git a/core/block_validator.go b/core/block_validator.go index 4f51f5dc17..04102a5efb 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -145,9 +145,9 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD } // Validate the parsed requests match the expected header value. if header.RequestsHash != nil { - depositSha := types.DeriveSha(res.Requests, trie.NewStackTrie(nil)) - if depositSha != *header.RequestsHash { - return fmt.Errorf("invalid deposit root hash (remote: %x local: %x)", *header.RequestsHash, depositSha) + reqhash := types.CalcRequestHash(res.Requests) + if reqhash != *header.RequestsHash { + return fmt.Errorf("invalid deposit root hash (remote: %x local: %x)", *header.RequestsHash, reqhash) } } // Validate the state root against the received state root and throw diff --git a/core/blockchain_test.go b/core/blockchain_test.go index d157a7bc3c..c370172012 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4228,89 +4228,90 @@ func TestEIP3651(t *testing.T) { } } -func TestEIP6110(t *testing.T) { - var ( - engine = beacon.NewFaker() - - // A sender who makes transactions, has some funds - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - addr = crypto.PubkeyToAddress(key.PublicKey) - funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) - config = *params.AllEthashProtocolChanges - gspec = &Genesis{ - Config: &config, - Alloc: types.GenesisAlloc{ - addr: {Balance: funds}, - config.DepositContractAddress: { - // Simple deposit generator, source: https://gist.github.com/lightclient/54abb2af2465d6969fa6d1920b9ad9d7 - Code: common.Hex2Bytes("6080604052366103aa575f603067ffffffffffffffff811115610025576100246103ae565b5b6040519080825280601f01601f1916602001820160405280156100575781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061007d5761007c6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f602067ffffffffffffffff8111156100c7576100c66103ae565b5b6040519080825280601f01601f1916602001820160405280156100f95781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061011f5761011e6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff811115610169576101686103ae565b5b6040519080825280601f01601f19166020018201604052801561019b5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f815181106101c1576101c06103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f606067ffffffffffffffff81111561020b5761020a6103ae565b5b6040519080825280601f01601f19166020018201604052801561023d5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610263576102626103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff8111156102ad576102ac6103ae565b5b6040519080825280601f01601f1916602001820160405280156102df5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610305576103046103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f8081819054906101000a900460ff168092919061035090610441565b91906101000a81548160ff021916908360ff160217905550507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c585858585856040516103a09594939291906104d9565b60405180910390a1005b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60ff82169050919050565b5f61044b82610435565b915060ff820361045e5761045d610408565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6104ab82610469565b6104b58185610473565b93506104c5818560208601610483565b6104ce81610491565b840191505092915050565b5f60a0820190508181035f8301526104f181886104a1565b9050818103602083015261050581876104a1565b9050818103604083015261051981866104a1565b9050818103606083015261052d81856104a1565b9050818103608083015261054181846104a1565b9050969550505050505056fea26469706673582212208569967e58690162d7d6fe3513d07b393b4c15e70f41505cbbfd08f53eba739364736f6c63430008190033"), - Nonce: 0, - Balance: big.NewInt(0), - }, - }, - } - ) - - gspec.Config.BerlinBlock = common.Big0 - gspec.Config.LondonBlock = common.Big0 - gspec.Config.TerminalTotalDifficulty = common.Big0 - gspec.Config.TerminalTotalDifficultyPassed = true - gspec.Config.ShanghaiTime = u64(0) - gspec.Config.CancunTime = u64(0) - gspec.Config.PragueTime = u64(0) - signer := types.LatestSigner(gspec.Config) - - _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { - for i := 0; i < 5; i++ { - txdata := &types.DynamicFeeTx{ - ChainID: gspec.Config.ChainID, - Nonce: uint64(i), - To: &config.DepositContractAddress, - Gas: 500000, - GasFeeCap: newGwei(5), - GasTipCap: big.NewInt(2), - AccessList: nil, - Data: []byte{}, - } - tx := types.NewTx(txdata) - tx, _ = types.SignTx(tx, signer, key) - b.AddTx(tx) - } - }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{DisableStack: true}, os.Stderr).Hooks()}, nil) - if err != nil { - t.Fatalf("failed to create tester chain: %v", err) - } - defer chain.Stop() - if n, err := chain.InsertChain(blocks); err != nil { - t.Fatalf("block %d: failed to insert into chain: %v", n, err) - } - - block := chain.GetBlockByNumber(1) - if len(block.Requests()) != 5 { - t.Fatalf("failed to retrieve deposits: have %d, want %d", len(block.Requests()), 5) - } - - // Verify each index is correct. - for want, req := range block.Requests() { - d, ok := req.Inner().(*types.Deposit) - if !ok { - t.Fatalf("expected deposit object") - } - if got := int(d.PublicKey[0]); got != want { - t.Fatalf("invalid pubkey: have %d, want %d", got, want) - } - if got := int(d.WithdrawalCredentials[0]); got != want { - t.Fatalf("invalid withdrawal credentials: have %d, want %d", got, want) - } - if d.Amount != uint64(want) { - t.Fatalf("invalid amounbt: have %d, want %d", d.Amount, want) - } - if got := int(d.Signature[0]); got != want { - t.Fatalf("invalid signature: have %d, want %d", got, want) - } - if d.Index != uint64(want) { - t.Fatalf("invalid index: have %d, want %d", d.Index, want) - } - } -} +// This test checks that deposit contract logs are turned into requests. +// func TestEIP6110(t *testing.T) { +// var ( +// engine = beacon.NewFaker() +// +// // A sender who makes transactions, has some funds +// key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") +// addr = crypto.PubkeyToAddress(key.PublicKey) +// funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) +// config = *params.AllEthashProtocolChanges +// gspec = &Genesis{ +// Config: &config, +// Alloc: types.GenesisAlloc{ +// addr: {Balance: funds}, +// config.DepositContractAddress: { +// // Simple deposit generator, source: https://gist.github.com/lightclient/54abb2af2465d6969fa6d1920b9ad9d7 +// Code: common.Hex2Bytes("6080604052366103aa575f603067ffffffffffffffff811115610025576100246103ae565b5b6040519080825280601f01601f1916602001820160405280156100575781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061007d5761007c6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f602067ffffffffffffffff8111156100c7576100c66103ae565b5b6040519080825280601f01601f1916602001820160405280156100f95781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061011f5761011e6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff811115610169576101686103ae565b5b6040519080825280601f01601f19166020018201604052801561019b5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f815181106101c1576101c06103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f606067ffffffffffffffff81111561020b5761020a6103ae565b5b6040519080825280601f01601f19166020018201604052801561023d5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610263576102626103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff8111156102ad576102ac6103ae565b5b6040519080825280601f01601f1916602001820160405280156102df5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610305576103046103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f8081819054906101000a900460ff168092919061035090610441565b91906101000a81548160ff021916908360ff160217905550507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c585858585856040516103a09594939291906104d9565b60405180910390a1005b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60ff82169050919050565b5f61044b82610435565b915060ff820361045e5761045d610408565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6104ab82610469565b6104b58185610473565b93506104c5818560208601610483565b6104ce81610491565b840191505092915050565b5f60a0820190508181035f8301526104f181886104a1565b9050818103602083015261050581876104a1565b9050818103604083015261051981866104a1565b9050818103606083015261052d81856104a1565b9050818103608083015261054181846104a1565b9050969550505050505056fea26469706673582212208569967e58690162d7d6fe3513d07b393b4c15e70f41505cbbfd08f53eba739364736f6c63430008190033"), +// Nonce: 0, +// Balance: big.NewInt(0), +// }, +// }, +// } +// ) +// +// gspec.Config.BerlinBlock = common.Big0 +// gspec.Config.LondonBlock = common.Big0 +// gspec.Config.TerminalTotalDifficulty = common.Big0 +// gspec.Config.TerminalTotalDifficultyPassed = true +// gspec.Config.ShanghaiTime = u64(0) +// gspec.Config.CancunTime = u64(0) +// gspec.Config.PragueTime = u64(0) +// signer := types.LatestSigner(gspec.Config) +// +// _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { +// for i := 0; i < 5; i++ { +// txdata := &types.DynamicFeeTx{ +// ChainID: gspec.Config.ChainID, +// Nonce: uint64(i), +// To: &config.DepositContractAddress, +// Gas: 500000, +// GasFeeCap: newGwei(5), +// GasTipCap: big.NewInt(2), +// AccessList: nil, +// Data: []byte{}, +// } +// tx := types.NewTx(txdata) +// tx, _ = types.SignTx(tx, signer, key) +// b.AddTx(tx) +// } +// }) +// chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{DisableStack: true}, os.Stderr).Hooks()}, nil) +// if err != nil { +// t.Fatalf("failed to create tester chain: %v", err) +// } +// defer chain.Stop() +// if n, err := chain.InsertChain(blocks); err != nil { +// t.Fatalf("block %d: failed to insert into chain: %v", n, err) +// } +// +// block := chain.GetBlockByNumber(1) +// if len(block.Requests()) != 5 { +// t.Fatalf("failed to retrieve deposits: have %d, want %d", len(block.Requests()), 5) +// } +// +// // Verify each index is correct. +// for want, req := range block.Requests() { +// d, ok := req.Inner().(*types.Deposit) +// if !ok { +// t.Fatalf("expected deposit object") +// } +// if got := int(d.PublicKey[0]); got != want { +// t.Fatalf("invalid pubkey: have %d, want %d", got, want) +// } +// if got := int(d.WithdrawalCredentials[0]); got != want { +// t.Fatalf("invalid withdrawal credentials: have %d, want %d", got, want) +// } +// if d.Amount != uint64(want) { +// t.Fatalf("invalid amounbt: have %d, want %d", d.Amount, want) +// } +// if got := int(d.Signature[0]); got != want { +// t.Fatalf("invalid signature: have %d, want %d", got, want) +// } +// if d.Index != uint64(want) { +// t.Fatalf("invalid index: have %d, want %d", d.Index, want) +// } +// } +// } diff --git a/core/chain_makers.go b/core/chain_makers.go index 8e75abdea0..6df935d1f7 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -346,14 +346,14 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse gen(i, b) } - var requests types.Requests + var requests [][]byte if config.IsPrague(b.header.Number, b.header.Time) { for _, r := range b.receipts { - d, err := ParseDepositLogs(r.Logs, config) + req, err := ParseDepositLogs(r.Logs, config) if err != nil { panic(fmt.Sprintf("failed to parse deposit log: %v", err)) } - requests = append(requests, d...) + requests = append(requests, req...) } } diff --git a/core/genesis.go b/core/genesis.go index 31db49f527..79055e7bff 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -449,7 +449,7 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { } var ( withdrawals []*types.Withdrawal - requests types.Requests + requests [][]byte ) if conf := g.Config; conf != nil { num := big.NewInt(int64(g.Number)) @@ -474,7 +474,7 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { } if conf.IsPrague(num, g.Timestamp) { head.RequestsHash = &types.EmptyRequestsHash - requests = make(types.Requests, 0) + requests = make([][]byte, 0) } } return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil)) diff --git a/core/state_processor.go b/core/state_processor.go index c74a33c378..1c258ca84d 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -97,7 +97,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg allLogs = append(allLogs, receipt.Logs...) } // Read requests if Prague is enabled. - var requests types.Requests + var requests [][]byte if p.config.IsPrague(block.Number(), block.Time()) { requests, err = ParseDepositLogs(allLogs, p.config) if err != nil { @@ -262,15 +262,15 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state. // ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by // BeaconDepositContract. -func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) (types.Requests, error) { - deposits := make(types.Requests, 0) +func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) ([][]byte, error) { + deposits := make([][]byte, 0) for _, log := range logs { if log.Address == config.DepositContractAddress { - d, err := types.UnpackIntoDeposit(log.Data) + request, err := types.DepositLogToRequest(log.Data) if err != nil { return nil, fmt.Errorf("unable to parse deposit data: %v", err) } - deposits = append(deposits, types.NewRequest(d)) + deposits = append(deposits, request) } } return deposits, nil diff --git a/core/types.go b/core/types.go index 65cd4973e4..bed20802ab 100644 --- a/core/types.go +++ b/core/types.go @@ -54,7 +54,7 @@ type Processor interface { // ProcessResult contains the values computed by Process. type ProcessResult struct { Receipts types.Receipts - Requests types.Requests + Requests [][]byte Logs []*types.Log GasUsed uint64 } diff --git a/core/types/block.go b/core/types/block.go index 1c00658d5b..e6cbc65887 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -184,7 +184,7 @@ type Body struct { Transactions []*Transaction Uncles []*Header Withdrawals []*Withdrawal `rlp:"optional"` - Requests []*Request `rlp:"optional"` + Requests [][]byte `rlp:"optional"` } // Block represents an Ethereum block. @@ -209,7 +209,7 @@ type Block struct { uncles []*Header transactions Transactions withdrawals Withdrawals - requests Requests + requests [][]byte // witness is not an encoded part of the block body. // It is held in Block in order for easy relaying to the places @@ -232,7 +232,7 @@ type extblock struct { Txs []*Transaction Uncles []*Header Withdrawals []*Withdrawal `rlp:"optional"` - Requests []*Request `rlp:"optional"` + Requests [][]byte `rlp:"optional"` } // NewBlock creates a new block. The input data is copied, changes to header and to the @@ -292,9 +292,9 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher b.header.RequestsHash = nil } else if len(requests) == 0 { b.header.RequestsHash = &EmptyRequestsHash - b.requests = Requests{} + b.requests = [][]byte{} } else { - h := DeriveSha(Requests(requests), hasher) + h := CalcRequestHash(requests) b.header.RequestsHash = &h b.requests = slices.Clone(requests) } @@ -376,7 +376,7 @@ func (b *Block) Body() *Body { func (b *Block) Uncles() []*Header { return b.uncles } func (b *Block) Transactions() Transactions { return b.transactions } func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } -func (b *Block) Requests() Requests { return b.requests } +func (b *Block) Requests() [][]byte { return b.requests } func (b *Block) Transaction(hash common.Hash) *Transaction { for _, transaction := range b.transactions { @@ -474,6 +474,13 @@ func CalcUncleHash(uncles []*Header) common.Hash { return rlpHash(uncles) } +func CalcRequestHash(requests [][]byte) common.Hash { + if len(requests) == 0 { + return EmptyRequestsHash + } + return rlpHash(requests) +} + // NewBlockWithHeader creates a block with the given header data. The // header data is copied, changes to header and to the field values // will not affect the block. diff --git a/core/types/deposit.go b/core/types/deposit.go index 172acc36ed..18f5659a35 100644 --- a/core/types/deposit.go +++ b/core/types/deposit.go @@ -17,52 +17,29 @@ package types import ( - "bytes" - "encoding/binary" "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" ) -//go:generate go run github.com/fjl/gencodec -type Deposit -field-override depositMarshaling -out gen_deposit_json.go - -// Deposit contains EIP-6110 deposit data. -type Deposit struct { - PublicKey [48]byte `json:"pubkey"` // public key of validator - WithdrawalCredentials common.Hash `json:"withdrawalCredentials"` // beneficiary of the validator funds - Amount uint64 `json:"amount"` // deposit size in Gwei - Signature [96]byte `json:"signature"` // signature over deposit msg - Index uint64 `json:"index"` // deposit count value -} - -// field type overrides for gencodec -type depositMarshaling struct { - PublicKey hexutil.Bytes - WithdrawalCredentials hexutil.Bytes - Amount hexutil.Uint64 - Signature hexutil.Bytes - Index hexutil.Uint64 -} - -// Deposits implements DerivableList for requests. -type Deposits []*Deposit - -// Len returns the length of s. -func (s Deposits) Len() int { return len(s) } - -// EncodeIndex encodes the i'th deposit to s. -func (s Deposits) EncodeIndex(i int, w *bytes.Buffer) { - rlp.Encode(w, s[i]) -} +const ( + depositRequestType = 0x01 + depositRequestSize = 192 + 1 +) // UnpackIntoDeposit unpacks a serialized DepositEvent. -func UnpackIntoDeposit(data []byte) (*Deposit, error) { +func DepositLogToRequest(data []byte) ([]byte, error) { if len(data) != 576 { return nil, fmt.Errorf("deposit wrong length: want 576, have %d", len(data)) } - var d Deposit + + var outputRequest = make([]byte, depositRequestSize) + outputRequest[0] = depositRequestSize + const ( + pubkeyOffset = 1 + withdrawalCredOffset = pubkeyOffset + 48 + amountOffset = withdrawalCredOffset + 32 + signatureOffset = amountOffset + 8 + indexOffset = signatureOffset + 96 + ) // The ABI encodes the position of dynamic elements first. Since there are 5 // elements, skip over the positional data. The first 32 bytes of dynamic // elements also encode their actual length. Skip over that value too. @@ -70,34 +47,20 @@ func UnpackIntoDeposit(data []byte) (*Deposit, error) { // PublicKey is the first element. ABI encoding pads values to 32 bytes, so // despite BLS public keys being length 48, the value length here is 64. Then // skip over the next length value. - copy(d.PublicKey[:], data[b:b+48]) + copy(outputRequest[pubkeyOffset:], data[b:b+48]) b += 48 + 16 + 32 // WithdrawalCredentials is 32 bytes. Read that value then skip over next // length. - copy(d.WithdrawalCredentials[:], data[b:b+32]) + copy(outputRequest[withdrawalCredOffset:], data[b:b+32]) b += 32 + 32 // Amount is 8 bytes, but it is padded to 32. Skip over it and the next // length. - d.Amount = binary.LittleEndian.Uint64(data[b : b+8]) + copy(outputRequest[amountOffset:], data[b:b+8]) b += 8 + 24 + 32 // Signature is 96 bytes. Skip over it and the next length. - copy(d.Signature[:], data[b:b+96]) + copy(outputRequest[signatureOffset:], data[b:b+96]) b += 96 + 32 - // Amount is 8 bytes. - d.Index = binary.LittleEndian.Uint64(data[b : b+8]) - - return &d, nil -} - -func (d *Deposit) requestType() byte { return DepositRequestType } -func (d *Deposit) encode(b *bytes.Buffer) error { return rlp.Encode(b, d) } -func (d *Deposit) decode(input []byte) error { return rlp.DecodeBytes(input, d) } -func (d *Deposit) copy() RequestData { - return &Deposit{ - PublicKey: d.PublicKey, - WithdrawalCredentials: d.WithdrawalCredentials, - Amount: d.Amount, - Signature: d.Signature, - Index: d.Index, - } + // Index is 8 bytes. + copy(outputRequest[indexOffset:], data[b:b+8]) + return outputRequest, nil } diff --git a/core/types/gen_deposit_json.go b/core/types/gen_deposit_json.go deleted file mode 100644 index a65691188f..0000000000 --- a/core/types/gen_deposit_json.go +++ /dev/null @@ -1,70 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package types - -import ( - "encoding/json" - "errors" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*depositMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (d Deposit) MarshalJSON() ([]byte, error) { - type Deposit struct { - PublicKey hexutil.Bytes `json:"pubkey"` - WithdrawalCredentials hexutil.Bytes `json:"withdrawalCredentials"` - Amount hexutil.Uint64 `json:"amount"` - Signature hexutil.Bytes `json:"signature"` - Index hexutil.Uint64 `json:"index"` - } - var enc Deposit - enc.PublicKey = d.PublicKey[:] - enc.WithdrawalCredentials = d.WithdrawalCredentials[:] - enc.Amount = hexutil.Uint64(d.Amount) - enc.Signature = d.Signature[:] - enc.Index = hexutil.Uint64(d.Index) - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (d *Deposit) UnmarshalJSON(input []byte) error { - type Deposit struct { - PublicKey *hexutil.Bytes `json:"pubkey"` - WithdrawalCredentials *hexutil.Bytes `json:"withdrawalCredentials"` - Amount *hexutil.Uint64 `json:"amount"` - Signature *hexutil.Bytes `json:"signature"` - Index *hexutil.Uint64 `json:"index"` - } - var dec Deposit - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.PublicKey != nil { - if len(*dec.PublicKey) != len(d.PublicKey) { - return errors.New("field 'pubkey' has wrong length, need 48 items") - } - copy(d.PublicKey[:], *dec.PublicKey) - } - if dec.WithdrawalCredentials != nil { - if len(*dec.WithdrawalCredentials) != len(d.WithdrawalCredentials) { - return errors.New("field 'withdrawalCredentials' has wrong length, need 32 items") - } - copy(d.WithdrawalCredentials[:], *dec.WithdrawalCredentials) - } - if dec.Amount != nil { - d.Amount = uint64(*dec.Amount) - } - if dec.Signature != nil { - if len(*dec.Signature) != len(d.Signature) { - return errors.New("field 'signature' has wrong length, need 96 items") - } - copy(d.Signature[:], *dec.Signature) - } - if dec.Index != nil { - d.Index = uint64(*dec.Index) - } - return nil -} diff --git a/core/types/hashes.go b/core/types/hashes.go index cbd197072e..33f543e80c 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -42,7 +42,7 @@ var ( EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") // EmptyRequestsHash is the known hash of the empty requests set. - EmptyRequestsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + EmptyRequestsHash = common.HexToHash("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") // EmptyVerkleHash is the known hash of an empty verkle trie. EmptyVerkleHash = common.Hash{} diff --git a/core/types/request.go b/core/types/request.go deleted file mode 100644 index 7b1cade26e..0000000000 --- a/core/types/request.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2024 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package types - -import ( - "bytes" - "errors" - "fmt" - "io" - - "github.com/ethereum/go-ethereum/rlp" -) - -var ( - ErrRequestTypeNotSupported = errors.New("request type not supported") - errShortTypedRequest = errors.New("typed request too short") -) - -// Request types. -const ( - DepositRequestType = 0x00 -) - -// Request is an EIP-7685 request object. It represents execution layer -// triggered messages bound for the consensus layer. -type Request struct { - inner RequestData -} - -// Type returns the EIP-7685 type of the request. -func (r *Request) Type() byte { - return r.inner.requestType() -} - -// Inner returns the inner request data. -func (r *Request) Inner() RequestData { - return r.inner -} - -// NewRequest creates a new request. -func NewRequest(inner RequestData) *Request { - req := new(Request) - req.inner = inner.copy() - return req -} - -// Requests implements DerivableList for requests. -type Requests []*Request - -// Len returns the length of s. -func (s Requests) Len() int { return len(s) } - -// EncodeIndex encodes the i'th request to s. -func (s Requests) EncodeIndex(i int, w *bytes.Buffer) { - s[i].encode(w) -} - -// RequestData is the underlying data of a request. -type RequestData interface { - requestType() byte - encode(*bytes.Buffer) error - decode([]byte) error - copy() RequestData // creates a deep copy and initializes all fields -} - -// EncodeRLP implements rlp.Encoder -func (r *Request) EncodeRLP(w io.Writer) error { - buf := encodeBufferPool.Get().(*bytes.Buffer) - defer encodeBufferPool.Put(buf) - buf.Reset() - if err := r.encode(buf); err != nil { - return err - } - return rlp.Encode(w, buf.Bytes()) -} - -// encode writes the canonical encoding of a request to w. -func (r *Request) encode(w *bytes.Buffer) error { - w.WriteByte(r.Type()) - return r.inner.encode(w) -} - -// MarshalBinary returns the canonical encoding of the request. -func (r *Request) MarshalBinary() ([]byte, error) { - var buf bytes.Buffer - err := r.encode(&buf) - return buf.Bytes(), err -} - -// DecodeRLP implements rlp.Decoder -func (r *Request) DecodeRLP(s *rlp.Stream) error { - kind, size, err := s.Kind() - switch { - case err != nil: - return err - case kind == rlp.List: - return fmt.Errorf("untyped request") - case kind == rlp.Byte: - return errShortTypedRequest - default: - // First read the request payload bytes into a temporary buffer. - b, buf, err := getPooledBuffer(size) - if err != nil { - return err - } - defer encodeBufferPool.Put(buf) - if err := s.ReadBytes(b); err != nil { - return err - } - // Now decode the inner request. - inner, err := r.decode(b) - if err == nil { - r.inner = inner - } - return err - } -} - -// UnmarshalBinary decodes the canonical encoding of requests. -func (r *Request) UnmarshalBinary(b []byte) error { - inner, err := r.decode(b) - if err != nil { - return err - } - r.inner = inner - return nil -} - -// decode decodes a request from the canonical format. -func (r *Request) decode(b []byte) (RequestData, error) { - if len(b) <= 1 { - return nil, errShortTypedRequest - } - var inner RequestData - switch b[0] { - case DepositRequestType: - inner = new(Deposit) - default: - return nil, ErrRequestTypeNotSupported - } - err := inner.decode(b[1:]) - return inner, err -} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 991cdf93f3..ebe51b6450 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -604,8 +604,8 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas if params.BlobGasUsed == nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun")) } - if params.Deposits == nil { - return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil deposits post-prague")) + if params.Requests == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil requests post-prague")) } if versionedHashes == nil { @@ -842,7 +842,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe "params.ExcessBlobGas", ebg, "len(params.Transactions)", len(params.Transactions), "len(params.Withdrawals)", len(params.Withdrawals), - "len(params.Deposits)", len(params.Deposits), + "len(params.Requests)", len(params.Requests), "beaconRoot", beaconRoot, "error", err) return api.invalid(err, nil), nil @@ -1189,7 +1189,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin if body != nil { // Nil out the V2 values, clients should know to not request V1 objects // after Prague. - body.Deposits = nil + body.Requests = nil } bodies[i] = body } @@ -1218,7 +1218,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) // after Prague. for i := range bodies { if bodies[i] != nil { - bodies[i].Deposits = nil + bodies[i].Requests = nil } } return bodies, nil @@ -1257,35 +1257,26 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody { } var ( - body = block.Body() - txs = make([]hexutil.Bytes, len(body.Transactions)) - withdrawals = body.Withdrawals - depositRequests types.Deposits + body = block.Body() + result engine.ExecutionPayloadBody ) + result.TransactionData = make([]hexutil.Bytes, len(body.Transactions)) for j, tx := range body.Transactions { - txs[j], _ = tx.MarshalBinary() + result.TransactionData[j], _ = tx.MarshalBinary() } // Post-shanghai withdrawals MUST be set to empty slice instead of nil - if withdrawals == nil && block.Header().WithdrawalsHash != nil { - withdrawals = make([]*types.Withdrawal, 0) + if body.Withdrawals != nil && block.Header().WithdrawalsHash != nil { + result.Withdrawals = make([]*types.Withdrawal, 0) } - if block.Header().RequestsHash != nil { - // TODO: this isn't future proof because we can't determine if a request - // type has activated yet or if there are just no requests of that type from - // only the block. - for _, req := range block.Requests() { - if d, ok := req.Inner().(*types.Deposit); ok { - depositRequests = append(depositRequests, d) - } + if reqs := block.Requests(); reqs != nil { + result.Requests = make([]hexutil.Bytes, len(reqs)) + for j, req := range reqs { + result.Requests[j] = req } } - return &engine.ExecutionPayloadBody{ - TransactionData: txs, - Withdrawals: withdrawals, - Deposits: depositRequests, - } + return &result } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 395deef615..ab0d2ae808 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1569,18 +1569,13 @@ func equalBody(a *types.Body, b *engine.ExecutionPayloadBody) bool { return false } - var deposits types.Deposits + var requests [][]byte if a.Requests != nil { - // If requests is non-nil, it means deposits are available in block and we + // If requests is non-nil, it means requests are available in block and we // should return an empty slice instead of nil if there are no deposits. - deposits = make(types.Deposits, 0) + requests = make([][]byte, 0) } - for _, r := range a.Requests { - if d, ok := r.Inner().(*types.Deposit); ok { - deposits = append(deposits, d) - } - } - return reflect.DeepEqual(deposits, b.Deposits) + return reflect.DeepEqual(requests, b.Requests) } func TestBlockToPayloadWithBlobs(t *testing.T) { diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index adad450200..23d25ed8fc 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -785,7 +785,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash, uncleLists [][]*types.Header, uncleListHashes []common.Hash, withdrawalLists [][]*types.Withdrawal, withdrawalListHashes []common.Hash, - requestsLists [][]*types.Request, requestsListHashes []common.Hash) (int, error) { + requestsLists [][][]byte, requestsListHashes []common.Hash) (int, error) { q.lock.Lock() defer q.lock.Unlock() diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 951352319f..b1465e6f8a 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -326,7 +326,7 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error { withdrawalHashes[i] = types.DeriveSha(types.Withdrawals(body.Withdrawals), hasher) } if body.Requests != nil { - requestsHashes[i] = types.DeriveSha(types.Requests(body.Requests), hasher) + requestsHashes[i] = types.CalcRequestHash(body.Requests) } } return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes, requestsHashes} diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index cbc895eabb..6c6c0fe839 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -224,17 +224,17 @@ type BlockBody struct { Transactions []*types.Transaction // Transactions contained within a block Uncles []*types.Header // Uncles contained within a block Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block - Requests []*types.Request `rlp:"optional"` // Requests contained within a block + Requests [][]byte `rlp:"optional"` // Requests contained within a block } // Unpack retrieves the transactions and uncles from the range packet and returns // them in a split flat format that's more consistent with the internal data structures. -func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal, [][]*types.Request) { +func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal, [][][]byte) { var ( txset = make([][]*types.Transaction, len(*p)) uncleset = make([][]*types.Header, len(*p)) withdrawalset = make([][]*types.Withdrawal, len(*p)) - requestset = make([][]*types.Request, len(*p)) + requestset = make([][][]byte, len(*p)) ) for i, body := range *p { txset[i], uncleset[i], withdrawalset[i], requestset[i] = body.Transactions, body.Uncles, body.Withdrawals, body.Requests diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index a1148bcedb..f5a0109ab9 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -123,7 +123,7 @@ type rpcBlock struct { Transactions []rpcTransaction `json:"transactions"` UncleHashes []common.Hash `json:"uncles"` Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` - Requests []*types.Request `json:"requests,omitempty"` + Requests []hexutil.Bytes `json:"requests,omitempty"` } func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { @@ -192,12 +192,22 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface } txs[i] = tx.tx } + + // Convert requests. + var requests [][]byte + if body.Requests != nil { + requests = make([][]byte, len(body.Requests)) + for i, req := range body.Requests { + requests[i] = req + } + } + return types.NewBlockWithHeader(head).WithBody( types.Body{ Transactions: txs, Uncles: uncles, Withdrawals: body.Withdrawals, - Requests: body.Requests, + Requests: requests, }), nil }