Skip to content

Commit ec98b1d

Browse files
eth/catalyst: implement kintsugi spec v1.0.0-alpha.3
1 parent c10a0a6 commit ec98b1d

File tree

7 files changed

+250
-173
lines changed

7 files changed

+250
-173
lines changed

eth/catalyst/api.go

Lines changed: 96 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package catalyst
1919

2020
import (
2121
"bytes"
22+
"crypto/sha256"
23+
"encoding/binary"
2224
"errors"
2325
"fmt"
2426
"math/big"
@@ -43,8 +45,9 @@ import (
4345

4446
var (
4547
VALID = GenericStringResponse{"VALID"}
46-
INVALID = GenericStringResponse{"INVALID"}
47-
SYNCING = GenericStringResponse{"SYNCING"}
48+
SUCCESS = GenericStringResponse{"SUCCESS"}
49+
INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil}
50+
SYNCING = ForkChoiceResponse{Status: "INVALID", PayloadID: nil}
4851
UnknownHeader = rpc.CustomError{Code: -32000, Message: "unknown header"}
4952
UnknownPayload = rpc.CustomError{Code: -32001, Message: "unknown payload"}
5053
)
@@ -82,7 +85,7 @@ type ConsensusAPI struct {
8285
eth *eth.Ethereum
8386
les *les.LightEthereum
8487
engine consensus.Engine // engine is the post-merge consensus engine, only for block creation
85-
preparedBlocks map[int]*ExecutableData
88+
preparedBlocks map[uint64]*ExecutableDataV1
8689
}
8790

8891
func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI {
@@ -111,7 +114,7 @@ func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI {
111114
eth: eth,
112115
les: les,
113116
engine: engine,
114-
preparedBlocks: make(map[int]*ExecutableData),
117+
preparedBlocks: make(map[uint64]*ExecutableDataV1),
115118
}
116119
}
117120

@@ -171,64 +174,87 @@ func (api *ConsensusAPI) makeEnv(parent *types.Block, header *types.Header) (*bl
171174
return env, nil
172175
}
173176

174-
func (api *ConsensusAPI) PreparePayload(params AssembleBlockParams) (*PayloadResponse, error) {
175-
data, err := api.assembleBlock(params)
176-
if err != nil {
177-
return nil, err
178-
}
179-
id := len(api.preparedBlocks)
180-
api.preparedBlocks[id] = data
181-
return &PayloadResponse{PayloadID: uint64(id)}, nil
182-
}
183-
184-
func (api *ConsensusAPI) GetPayload(PayloadID hexutil.Uint64) (*ExecutableData, error) {
185-
data, ok := api.preparedBlocks[int(PayloadID)]
177+
func (api *ConsensusAPI) GetPayloadV1(PayloadID hexutil.Bytes) (*ExecutableDataV1, error) {
178+
hash := []byte(PayloadID)
179+
id := binary.BigEndian.Uint64(hash[:8])
180+
data, ok := api.preparedBlocks[id]
186181
if !ok {
187182
return nil, &UnknownPayload
188183
}
189184
return data, nil
190185
}
191186

192-
// ConsensusValidated is called to mark a block as valid, so
193-
// that data that is no longer needed can be removed.
194-
func (api *ConsensusAPI) ConsensusValidated(params ConsensusValidatedParams) error {
195-
switch params.Status {
196-
case VALID.Status:
197-
return nil
198-
case INVALID.Status:
199-
// TODO (MariusVanDerWijden) delete the block from the bc
200-
return nil
201-
default:
202-
return errors.New("invalid params.status")
187+
func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads ForkchoiceStateV1, PayloadAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) {
188+
var emptyHash = common.Hash{}
189+
if !bytes.Equal(heads.HeadBlockHash[:], emptyHash[:]) {
190+
if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil {
191+
if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil {
192+
// TODO (MariusVanDerWijden) trigger sync
193+
return SYNCING, nil
194+
}
195+
return INVALID, err
196+
}
197+
// If the finalized block is set, check if it is in our blockchain
198+
if !bytes.Equal(heads.FinalizedBlockHash[:], emptyHash[:]) {
199+
if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil {
200+
// TODO (MariusVanDerWijden) trigger sync
201+
return SYNCING, nil
202+
}
203+
}
204+
// SetHead
205+
if err := api.setHead(heads.HeadBlockHash); err != nil {
206+
return INVALID, err
207+
}
208+
// Assemble block (if needed)
209+
if PayloadAttributes != nil {
210+
data, err := api.assembleBlock(heads.HeadBlockHash, PayloadAttributes)
211+
if err != nil {
212+
return INVALID, err
213+
}
214+
hash := computePayloadId(heads.HeadBlockHash, PayloadAttributes)
215+
id := binary.BigEndian.Uint64(hash)
216+
api.preparedBlocks[id] = data
217+
log.Info("Created payload", "payloadid", id)
218+
// TODO (MariusVanDerWijden) do something with the payloadID?
219+
hex := hexutil.Bytes(hash)
220+
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: &hex}, nil
221+
}
203222
}
223+
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil
204224
}
205225

206-
func (api *ConsensusAPI) ForkchoiceUpdated(params ForkChoiceParams) error {
207-
var emptyHash = common.Hash{}
208-
if !bytes.Equal(params.HeadBlockHash[:], emptyHash[:]) {
209-
if err := api.checkTerminalTotalDifficulty(params.HeadBlockHash); err != nil {
210-
return err
211-
}
212-
return api.setHead(params.HeadBlockHash)
226+
func computePayloadId(headBlockHash common.Hash, params *PayloadAttributesV1) []byte {
227+
input := make([]byte, 32+8+32+20)
228+
copy(input, headBlockHash[:])
229+
binary.BigEndian.PutUint64(input[32:], params.Timestamp)
230+
copy(input[40:], params.Random[:])
231+
copy(input[72:], params.FeeRecipient[:])
232+
hash := sha256.Sum256(input)
233+
return hash[:8]
234+
}
235+
236+
func (api *ConsensusAPI) invalid() ExecutePayloadResponse {
237+
if api.light {
238+
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()}
213239
}
214-
return nil
240+
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()}
215241
}
216242

217243
// ExecutePayload creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
218-
func (api *ConsensusAPI) ExecutePayload(params ExecutableData) (GenericStringResponse, error) {
244+
func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePayloadResponse, error) {
219245
block, err := ExecutableDataToBlock(params)
220246
if err != nil {
221-
return INVALID, err
247+
return api.invalid(), err
222248
}
223249
if api.light {
224250
parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash)
225251
if parent == nil {
226-
return INVALID, fmt.Errorf("could not find parent %x", params.ParentHash)
252+
return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash)
227253
}
228254
if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil {
229-
return INVALID, err
255+
return api.invalid(), err
230256
}
231-
return VALID, nil
257+
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
232258
}
233259
if !api.eth.BlockChain().HasBlock(block.ParentHash(), block.NumberU64()-1) {
234260
/*
@@ -237,37 +263,38 @@ func (api *ConsensusAPI) ExecutePayload(params ExecutableData) (GenericStringRes
237263
return SYNCING, err
238264
}
239265
*/
240-
return SYNCING, nil
266+
// TODO (MariusVanDerWijden) we should return nil here not empty hash
267+
return ExecutePayloadResponse{Status: SYNCING.Status, LatestValidHash: common.Hash{}}, nil
241268
}
242269
parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
243270
td := api.eth.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1)
244271
ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
245272
if td.Cmp(ttd) < 0 {
246-
return INVALID, fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
273+
return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
247274
}
248275
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
249-
return INVALID, err
276+
return api.invalid(), err
250277
}
251278
merger := api.merger()
252279
if !merger.TDDReached() {
253280
merger.ReachTTD()
254281
}
255-
return VALID, nil
282+
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
256283
}
257284

258285
// AssembleBlock creates a new block, inserts it into the chain, and returns the "execution
259286
// data" required for eth2 clients to process the new block.
260-
func (api *ConsensusAPI) assembleBlock(params AssembleBlockParams) (*ExecutableData, error) {
287+
func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAttributesV1) (*ExecutableDataV1, error) {
261288
if api.light {
262289
return nil, errors.New("not supported")
263290
}
264-
log.Info("Producing block", "parentHash", params.ParentHash)
291+
log.Info("Producing block", "parentHash", parentHash)
265292

266293
bc := api.eth.BlockChain()
267-
parent := bc.GetBlockByHash(params.ParentHash)
294+
parent := bc.GetBlockByHash(parentHash)
268295
if parent == nil {
269-
log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", params.ParentHash)
270-
return nil, fmt.Errorf("cannot assemble block with unknown parent %s", params.ParentHash)
296+
log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", parentHash)
297+
return nil, fmt.Errorf("cannot assemble block with unknown parent %s", parentHash)
271298
}
272299

273300
if params.Timestamp < parent.Time() {
@@ -376,7 +403,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
376403
return txs, nil
377404
}
378405

379-
func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
406+
func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
380407
txs, err := decodeTransactions(params.Transactions)
381408
if err != nil {
382409
return nil, err
@@ -410,8 +437,8 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
410437
return block, nil
411438
}
412439

413-
func BlockToExecutableData(block *types.Block, random common.Hash) *ExecutableData {
414-
return &ExecutableData{
440+
func BlockToExecutableData(block *types.Block, random common.Hash) *ExecutableDataV1 {
441+
return &ExecutableDataV1{
415442
BlockHash: block.Hash(),
416443
ParentHash: block.ParentHash(),
417444
Coinbase: block.Coinbase(),
@@ -447,11 +474,7 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
447474
if newHeadBlock == nil {
448475
return &UnknownHeader
449476
}
450-
parent := api.eth.BlockChain().GetBlockByHash(newHeadBlock.ParentHash())
451-
if parent == nil {
452-
return fmt.Errorf("parent unavailable: %v", newHeadBlock.ParentHash())
453-
}
454-
td := api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64())
477+
td := api.eth.BlockChain().GetTd(newHeadBlock.Hash(), newHeadBlock.NumberU64())
455478
if td != nil && td.Cmp(api.eth.BlockChain().Config().TerminalTotalDifficulty) < 0 {
456479
return errors.New("total difficulty not reached yet")
457480
}
@@ -460,11 +483,6 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
460483

461484
// setHead is called to perform a force choice.
462485
func (api *ConsensusAPI) setHead(newHead common.Hash) error {
463-
// Trigger the transition if it's the first `NewHead` event.
464-
merger := api.merger()
465-
if !merger.PoSFinalized() {
466-
merger.FinalizePoS()
467-
}
468486
log.Info("Setting head", "head", newHead)
469487
if api.light {
470488
headHeader := api.les.BlockChain().CurrentHeader()
@@ -478,10 +496,20 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error {
478496
if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil {
479497
return err
480498
}
499+
// Trigger the transition if it's the first `NewHead` event.
500+
merger := api.merger()
501+
if !merger.PoSFinalized() {
502+
merger.FinalizePoS()
503+
}
481504
return nil
482505
}
483506
headBlock := api.eth.BlockChain().CurrentBlock()
484507
if headBlock.Hash() == newHead {
508+
// Trigger the transition if it's the first `NewHead` event.
509+
merger := api.merger()
510+
if !merger.PoSFinalized() {
511+
merger.FinalizePoS()
512+
}
485513
return nil
486514
}
487515
newHeadBlock := api.eth.BlockChain().GetBlockByHash(newHead)
@@ -491,6 +519,12 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error {
491519
if err := api.eth.BlockChain().SetChainHead(newHeadBlock); err != nil {
492520
return err
493521
}
522+
// Trigger the transition if it's the first `NewHead` event.
523+
merger := api.merger()
524+
if !merger.PoSFinalized() {
525+
merger.FinalizePoS()
526+
}
527+
// TODO (MariusVanDerWijden) are we really synced now?
494528
api.eth.SetSynced()
495529
return nil
496530
}

0 commit comments

Comments
 (0)