From 7c88410dbb7b673cf81decf1f6c21441237950c8 Mon Sep 17 00:00:00 2001 From: DarkLord017 Date: Sat, 21 Sep 2024 14:59:13 +0530 Subject: [PATCH 1/9] Created consensus.go_util.go and edited consensuscore,rpc and types common --- common/types.go | 65 +- consensus/consensus.go | 1101 +++++++++++++++++++- consensus/consensus_core/consensus_core.go | 119 ++- consensus/rpc/consensus_rpc.go | 2 +- consensus/rpc/nimbus_rpc.go | 9 +- consensus/utils.go | 255 ++++- go.mod | 1 + go.sum | 2 + 8 files changed, 1447 insertions(+), 107 deletions(-) diff --git a/common/types.go b/common/types.go index 30f3303..77fabeb 100644 --- a/common/types.go +++ b/common/types.go @@ -1,19 +1,16 @@ package common -//other option is to import a package of go-ethereum but that was weird import ( "encoding/json" "fmt" + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/holiman/uint256" - "strconv" ) -// need to confirm how such primitive types will be imported, -// -// Transaction https://docs.rs/alloy/latest/alloy/rpc/types/struct.Transaction.html -// address: 20 bytes -// B256: 32 bytes https://bluealloy.github.io/revm/docs/revm/precompile/primitives/type.B256.html type Address struct { Addr [20]byte } @@ -40,29 +37,65 @@ type Block struct { Transactions Transactions TransactionsRoot [32]byte Uncles [][32]byte + BlobGasUsed *uint64 + ExcessBlobGas *uint64 } -// an enum having 2 types- how to implement?? type Transactions struct { Hashes [][32]byte - Full []types.Transaction //transaction needs to be defined + Full []Transaction // transaction needs to be defined +} + +type Transaction struct { + AccessList types.AccessList + Hash common.Hash + Nonce uint64 + BlockHash [32]byte + BlockNumber *uint64 + TransactionIndex uint64 + From string + To *common.Address + Value *big.Int + GasPrice *big.Int + Gas uint64 + Input []byte + ChainID *big.Int + TransactionType uint8 + Signature *Signature + MaxFeePerGas *big.Int + MaxPriorityFeePerGas *big.Int + MaxFeePerBlobGas *big.Int + BlobVersionedHashes []common.Hash +} + +type Signature struct { + R string + S string + V uint64 + YParity Parity +} + +type Parity struct { + Value bool } func Default() *Transactions { return &Transactions{ - Full: []types.Transaction{}, + Full: []Transaction{}, } } + func (t *Transactions) HashesFunc() [][32]byte { - if len(t.Hashes) > 0 { //if Transactions struct contains hashes then return them directly + if len(t.Hashes) > 0 { return t.Hashes } hashes := make([][32]byte, len(t.Full)) for i := range t.Full { - hashes[i] = t.Full[i].Hash() + hashes[i] = t.Full[i].Hash // Use the Hash field directly } return hashes } + func (t Transactions) MarshalJSON() ([]byte, error) { if len(t.Hashes) > 0 { return json.Marshal(t.Hashes) @@ -70,7 +103,6 @@ func (t Transactions) MarshalJSON() ([]byte, error) { return json.Marshal(t.Full) } -// can be an enum type BlockTag struct { Latest bool Finalized bool @@ -106,18 +138,21 @@ func (b *BlockTag) UnmarshalJSON(data []byte) error { } return nil } + func parseBlockNumber(block string) (uint64, error) { if len(block) > 2 && block[:2] == "0x" { return parseHexUint64(block[2:]) } return parseDecimalUint64(block) } + func parseHexUint64(hexStr string) (uint64, error) { return strconv.ParseUint(hexStr, 16, 64) } + func parseDecimalUint64(decStr string) (uint64, error) { return strconv.ParseUint(decStr, 10, 64) } -// need some error structs and enums as well -// Example BlockNotFoundError +// Example error structs can be defined here +// type BlockNotFoundError struct {} diff --git a/consensus/consensus.go b/consensus/consensus.go index 1fd67dd..28453b8 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -4,48 +4,1059 @@ package consensus // uses rpc // uses config for networks // uses common for datatypes -type ConsensusClient struct{} -type Inner struct{} -type LightClientStore struct{} - -func(con ConsensusClient) new(){} -func(con ConsensusClient) shutdown(){} -func(con ConsensusClient) expected_current_slot(){} - -func sync_fallback(){} -func sync_all_fallback(){} - - -func(in Inner) new(){} -func(in Inner) get_rpc(){} -func(in Inner) check_execution_payload(){} -func(in Inner) get_payloads(){} -func(in Inner) advance(){} -func(in Inner) send_blocks(){} -func(in Inner) duration_until_next_update(){} -func(in Inner) bootstrap(){} -func(in Inner) verify_generic_update(){} -func(in Inner) verify_update(){} -func(in Inner) verify_finality_update(){} -func(in Inner) verify_optimistic_update(){} -func(in Inner) apply_generic_update(){} -func(in Inner) apply_update(){} -func(in Inner) apply_finality_update(){} -func(in Inner) apply_optimistic_update(){} -func(in Inner) log_finality_update(){} -func(in Inner) log_optimistic_update(){} -func(in Inner) has_finality_update(){} -func(in Inner) has_sync_update(){} -func(in Inner) safety_threshold(){} -func(in Inner) verify_sync_committee_signature(){} -func(in Inner) compute_committee_sign_root(){} -func(in Inner) age(){} -func(in Inner) expected_current_slot(){} -func(in Inner) is_valid_checkpoint(){} - - - -func get_participating_keys(){} -func is_finality_proof_valid(){} -func is_current_committee_proof_valid(){} -func is_next_committee_proof_valid(){} \ No newline at end of file +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "log" + "math/big" + + "os" + "sync" + "time" + + "github.com/BlocSoc-iitr/selene/common" + "github.com/BlocSoc-iitr/selene/config" + "github.com/BlocSoc-iitr/selene/config/checkpoints" + "github.com/BlocSoc-iitr/selene/consensus/consensus_core" + "github.com/BlocSoc-iitr/selene/consensus/rpc" + "github.com/BlocSoc-iitr/selene/utils/bls" + geth "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" + "github.com/pkg/errors" +) + +// Error definitions + +var ( + ErrIncorrectRpcNetwork = errors.New("incorrect RPC network") + ErrPayloadNotFound = errors.New("payload not found") + ErrInvalidHeaderHash = errors.New("invalid header hash") + ErrCheckpointTooOld = errors.New("checkpoint too old") + ErrBootstrapFetchFailed = errors.New("could not fetch bootstrap") + ErrInvalidUpdate = errors.New("invalid update") + ErrInsufficientParticipation = errors.New("insufficient participation") + ErrInvalidTimestamp = errors.New("invalid timestamp") + ErrInvalidPeriod = errors.New("invalid period") + ErrNotRelevant = errors.New("update not relevant") + ErrInvalidFinalityProof = errors.New("invalid finality proof") + ErrInvalidNextSyncCommitteeProof = errors.New("invalid next sync committee proof") + ErrInvalidSignature = errors.New("invalid signature") +) + +const MAX_REQUEST_LIGHT_CLIENT_UPDATES = 128 + +type GenericUpdate struct { + AttestedHeader consensus_core.Header + SyncAggregate consensus_core.SyncAggregate + SignatureSlot uint64 + NextSyncCommittee *consensus_core.SyncCommittee + NextSyncCommitteeBranch *[]consensus_core.Bytes32 + FinalizedHeader consensus_core.Header + FinalityBranch []consensus_core.Bytes32 +} + +type ConsensusClient struct { + BlockRecv *common.Block + FinalizedBlockRecv *common.Block + CheckpointRecv *[]byte + genesisTime uint64 + db Database +} + +type Inner struct { + RPC rpc.ConsensusRpc + Store LightClientStore + lastCheckpoint *[]byte + blockSend chan common.Block + finalizedBlockSend chan *common.Block + checkpointSend chan *[]byte + Config *config.Config +} +type LightClientStore struct { + FinalizedHeader consensus_core.Header + CurrentSyncCommitee consensus_core.SyncCommittee + NextSyncCommitee *consensus_core.SyncCommittee + OptimisticHeader consensus_core.Header + PreviousMaxActiveParticipants uint64 + CurrentMaxActiveParticipants uint64 +} + +type Fork struct { + Version uint64 + Epoch uint64 +} + +func (con ConsensusClient) New(rpc *string, config config.Config) ConsensusClient { + blockSend := make(chan common.Block, 256) + finalizedBlockSend := make(chan *common.Block) + checkpointSend := make(chan *[]byte) + + db, err := con.db.New(&config) + if err != nil { + panic(err) + } + + var initialCheckpoint [32]byte + + if config.Checkpoint != nil { + initialNewCheckpoint, errorWhileLoadingCheckpoint := db.LoadCheckpoint() + copy(initialCheckpoint[:], initialNewCheckpoint) + if errorWhileLoadingCheckpoint != nil { + log.Printf("error while loading checkpoint: %v", errorWhileLoadingCheckpoint) + } + } + if &initialCheckpoint == nil { + panic("No checkpoint found") + } + In := &Inner{} + inner := In.New(*rpc, blockSend, finalizedBlockSend, checkpointSend, &config) + + go func() { + err := inner.sync(initialCheckpoint) + if err != nil { + if inner.Config.LoadExternalFallback { + err = sync_all_fallback(inner, inner.Config.Chain.ChainID) + if err != nil { + log.Printf("sync failed: %v", err) + os.Exit(1) + } + } else if inner.Config.Fallback != nil { + err = sync_fallback(inner, inner.Config.Fallback) + if err != nil { + log.Printf("sync failed: %v", err) + os.Exit(1) + } + } else { + log.Printf("sync failed: %v", err) + os.Exit(1) + } + } + + _ = inner.send_blocks() + + for { + time.Sleep(inner.duration_until_next_update()) + + err := inner.advance() + if err != nil { + log.Printf("advance error: %v", err) + continue + } + + err = inner.send_blocks() + if err != nil { + log.Printf("send error: %v", err) + continue + } + } + }() + + blocksReceived := <-blockSend + finalizedBlocksReceived := <-finalizedBlockSend + checkpointsReceived := <-checkpointSend + + return ConsensusClient{ + BlockRecv: &blocksReceived, + FinalizedBlockRecv: finalizedBlocksReceived, + CheckpointRecv: checkpointsReceived, + genesisTime: config.Chain.GenesisTime, + db: db, + } + +} +func (con ConsensusClient) Shutdown() error { + checkpoint := con.CheckpointRecv + if checkpoint != nil { + err := con.db.SaveCheckpoint(*checkpoint) + if err != nil { + return err + } + } + return nil +} +func (con ConsensusClient) expected_current_slot() uint64 { + now := time.Now().Unix() + // Assuming SLOT_DURATION is the duration of each slot in seconds + const SLOT_DURATION uint64 = 12 + return (uint64(now) - con.genesisTime) / SLOT_DURATION +} + +func sync_fallback(inner *Inner, fallback *string) error { + cf, err := (&checkpoints.CheckpointFallback{}).FetchLatestCheckpointFromApi(*fallback) + if err != nil { + return errors.Wrap(err, "failed to fetch checkpoint from API") + } + return inner.sync(cf) + +} +func sync_all_fallback(inner *Inner, chainID uint64) error { + var n config.Network + network, err := n.ChainID(chainID) + + ch := checkpoints.CheckpointFallback{} + + checkpointFallback, err := ch.Build() + if err != nil { + return err + } + + chainId := network.Chain.ChainID + var networkName config.Network + if chainId == 1 { + networkName = config.MAINNET + } else if chainId == 5 { + networkName = config.GOERLI + } else if chainId == 11155111 { + networkName = config.SEPOLIA + } else { + return errors.New("chain id not recognized") + } + + // Fetch the latest checkpoint from the network + checkpoint := checkpointFallback.FetchLatestCheckpoint(networkName) + if err != nil { + return err + } + + // Sync using the inner struct's sync method + if err := inner.sync(checkpoint); err != nil { + return err + } + + return nil +} + +func (in *Inner) New(rpcURL string, blockSend chan common.Block, finalizedBlockSend chan *common.Block, checkpointSend chan *[]byte, config *config.Config) *Inner { + rpcClient := rpc.NewConsensusRpc(rpcURL) + + return &Inner{ + RPC: rpcClient, + Store: LightClientStore{}, + lastCheckpoint: nil, // No checkpoint initially + blockSend: blockSend, + finalizedBlockSend: finalizedBlockSend, + checkpointSend: checkpointSend, + Config: config, + } + +} +func (in *Inner) Get_rpc() error { + chainID, err := in.RPC.ChainId() + if err != nil { + return err + } + if chainID != in.Config.Chain.ChainID { + return ErrIncorrectRpcNetwork + } + return nil +} +func (in *Inner) check_execution_payload(ctx context.Context, slot *uint64) (*consensus_core.ExecutionPayload, error) { + block, err := in.RPC.GetBlock(*slot) + if err != nil { + return nil, err + } + + blockHash, err := TreeHashRoot(block.Body.ToBytes()) + if err != nil { + return nil, err + } + latestSlot := in.Store.OptimisticHeader.Slot + finalizedSlot := in.Store.FinalizedHeader.Slot + + var verifiedBlockHash []byte + var errGettingBlockHash error + + if *slot == latestSlot { + verifiedBlockHash, errGettingBlockHash = TreeHashRoot(in.Store.OptimisticHeader.ToBytes()) + if errGettingBlockHash != nil { + return nil, ErrPayloadNotFound + } + } else if *slot == finalizedSlot { + verifiedBlockHash, errGettingBlockHash = TreeHashRoot(in.Store.FinalizedHeader.ToBytes()) + if errGettingBlockHash != nil { + return nil, ErrPayloadNotFound + } + } else { + return nil, ErrPayloadNotFound + } + + // Compare the hashes + if !bytes.Equal(verifiedBlockHash, blockHash) { + return nil, fmt.Errorf("%w: expected %v but got %v", ErrInvalidHeaderHash, verifiedBlockHash, blockHash) + } + + payload := block.Body.ExecutionPayload + return &payload, nil +} + +func (in *Inner) get_payloads(ctx context.Context, startSlot, endSlot uint64) ([]interface{}, error) { + var payloads []interface{} + + // Fetch the block at endSlot to get the initial parent hash + endBlock, err := in.RPC.GetBlock(endSlot) + if err != nil { + return nil, err + } + endPayload := endBlock.Body.ExecutionPayload + prevParentHash := endPayload.ParentHash + + // Create a wait group to manage concurrent fetching + var wg sync.WaitGroup + payloadsChan := make(chan interface{}, endSlot-startSlot+1) + errorChan := make(chan error, 1) // Buffer for one error + + // Fetch blocks in parallel + for slot := endSlot; slot >= startSlot; slot-- { + wg.Add(1) + go func(slot uint64) { + defer wg.Done() + block, err := in.RPC.GetBlock(slot) + if err != nil { + errorChan <- err + return + } + payload := block.Body.ExecutionPayload + if payload.ParentHash != prevParentHash { + log.Printf("Error while backfilling blocks: expected block hash %v but got %v", prevParentHash) + return + } + prevParentHash = *&payload.ParentHash + payloadsChan <- *&payload + }(slot) + } + + // Close channels after all fetches are complete + go func() { + wg.Wait() + close(payloadsChan) + close(errorChan) + }() + + // Collect results and check for errors + for { + select { + case payload, ok := <-payloadsChan: + if !ok { + return payloads, nil + } + payloads = append(payloads, payload) + case err := <-errorChan: + return nil, err + } + } +} +func (in *Inner) advance() error { + // Fetch and apply finality update + finalityUpdate, err := in.RPC.GetFinalityUpdate() + if err != nil { + return err + } + if err := in.verify_finality_update(&finalityUpdate); err != nil { + return err + } + in.apply_finality_update(&finalityUpdate) + + // Fetch and apply optimistic update + optimisticUpdate, err := in.RPC.GetOptimisticUpdate() + if err != nil { + return err + } + if err := in.verify_optimistic_update(&optimisticUpdate); err != nil { + return err + } + in.apply_optimistic_update(&optimisticUpdate) + + // Check for sync committee update if it's not set + if in.Store.NextSyncCommitee == nil { + log.Printf("checking for sync committee update") + + currentPeriod := CalcSyncPeriod(in.Store.FinalizedHeader.Slot) + updates, err := in.RPC.GetUpdates(currentPeriod, 1) + if err != nil { + return err + } + + if len(updates) == 1 { + update := updates[0] + if err := in.verify_update(&update); err == nil { + log.Printf("updating sync committee") + in.apply_update(&update) + } + } + } + + return nil +} +func (in *Inner) sync(checkpoint [32]byte) error { + // Reset store and checkpoint + in.Store = LightClientStore{} + in.lastCheckpoint = nil + + // Perform bootstrap with the given checkpoint + in.bootstrap(checkpoint) + + // Calculate the current sync period + currentPeriod := CalcSyncPeriod(in.Store.FinalizedHeader.Slot) + + // Fetch updates + updates, err := in.RPC.GetUpdates(currentPeriod, MAX_REQUEST_LIGHT_CLIENT_UPDATES) + if err != nil { + return err + } + + // Apply updates + for _, update := range updates { + if err := in.verify_update(&update); err != nil { + return err + } + in.apply_update(&update) + } + + // Fetch and apply finality update + finalityUpdate, err := in.RPC.GetFinalityUpdate() + if err != nil { + return err + } + if err := in.verify_finality_update(&finalityUpdate); err != nil { + return err + } + in.apply_finality_update(&finalityUpdate) + + // Fetch and apply optimistic update + optimisticUpdate, err := in.RPC.GetOptimisticUpdate() + if err != nil { + return err + } + if err := in.verify_optimistic_update(&optimisticUpdate); err != nil { + return err + } + in.apply_optimistic_update(&optimisticUpdate) + + // Log the success message + log.Printf("consensus client in sync with checkpoint: 0x%s", hex.EncodeToString(checkpoint[:])) + + return nil +} +func (in *Inner) send_blocks() error { + // Get slot from the optimistic header + slot := in.Store.OptimisticHeader.Slot + payload, err := in.check_execution_payload(context.Background(), &slot) + if err != nil { + return err + } + + // Get finalized slot from the finalized header + finalizedSlot := in.Store.FinalizedHeader.Slot + finalizedPayload, err := in.check_execution_payload(context.Background(), &finalizedSlot) + if err != nil { + return err + } + + // Send payload converted to block over the BlockSend channel + go func() { + block, err := PayloadToBlock(payload) + if err != nil { + log.Printf("Error converting payload to block: %v", err) + return + } + in.blockSend <- *block + }() + + go func() { + block, err := PayloadToBlock(finalizedPayload) + if err != nil { + log.Printf("Error converting finalized payload to block: %v", err) + return + } + in.finalizedBlockSend <- block + }() + + // Send checkpoint over the CheckpointSend channel + go func() { + in.checkpointSend <- in.lastCheckpoint + }() + + return nil +} + +func (in *Inner) duration_until_next_update() time.Duration { + currentSlot := in.expected_current_slot() + nextSlot := currentSlot + 1 + nextSlotTimestamp := nextSlot*12 + in.Config.Chain.GenesisTime + + now := uint64(time.Now().Unix()) + timeToNextSlot := int64(nextSlotTimestamp - now) + nextUpdate := timeToNextSlot + 4 + + return time.Duration(nextUpdate) * time.Second +} +func (in *Inner) bootstrap(checkpoint [32]byte) { + bootstrap, errInBootstrap := in.RPC.GetBootstrap(checkpoint) + if errInBootstrap != nil { + log.Printf("failed to fetch bootstrap: %v", errInBootstrap) + return + } + + isValid := in.is_valid_checkpoint(bootstrap.Header.Slot) + if !isValid { + if in.Config.StrictCheckpointAge == true { + log.Printf("checkpoint too old, consider using a more recent checkpoint") + return + } else { + log.Printf("checkpoint too old, consider using a more recent checkpoint") + } + } + + verify_bootstrap(checkpoint, bootstrap) + apply_bootstrap(&in.Store, bootstrap) + +} +func verify_bootstrap(checkpoint [32]byte, bootstrap consensus_core.Bootstrap) { + isCommitteValid := isCurrentCommitteeProofValid(&bootstrap.Header, &bootstrap.CurrentSyncCommittee, bootstrap.CurrentSyncCommitteeBranch) + if !isCommitteValid { + log.Println("invalid current sync committee proof") + return + } + + headerHash, err := TreeHashRoot(bootstrap.Header.ToBytes()) + if err != nil { + log.Println("failed to hash header") + return + } + HeaderValid := bytes.Equal(headerHash[:], checkpoint[:]) + + if !HeaderValid { + log.Println("invalid header hash") + return + } + +} + +func apply_bootstrap(store *LightClientStore, bootstrap consensus_core.Bootstrap) { + store.FinalizedHeader = bootstrap.Header + store.CurrentSyncCommitee = bootstrap.CurrentSyncCommittee + store.NextSyncCommitee = nil + store.OptimisticHeader = bootstrap.Header + store.PreviousMaxActiveParticipants = 0 + store.CurrentMaxActiveParticipants = 0 + +} + +func (in *Inner) verify_generic_update(update *GenericUpdate, expectedCurrentSlot uint64, store *LightClientStore, genesisRoots []byte, forks config.Forks) error { + { + bits := getBits(update.SyncAggregate.SyncCommitteeBits) + if bits == 0 { + return ErrInsufficientParticipation + } + + updateFinalizedSlot := update.FinalizedHeader.Slot + validTime := expectedCurrentSlot >= update.SignatureSlot && + update.SignatureSlot > update.AttestedHeader.Slot && + update.AttestedHeader.Slot >= updateFinalizedSlot + + if !validTime { + return ErrInvalidTimestamp + } + + storePeriod := CalcSyncPeriod(store.FinalizedHeader.Slot) + updateSigPeriod := CalcSyncPeriod(update.SignatureSlot) + + var validPeriod bool + if store.NextSyncCommitee != nil { + validPeriod = updateSigPeriod == storePeriod || updateSigPeriod == storePeriod+1 + } else { + validPeriod = updateSigPeriod == storePeriod + } + + if !validPeriod { + return ErrInvalidPeriod + } + + updateAttestedPeriod := CalcSyncPeriod(update.AttestedHeader.Slot) + updateHasNextCommittee := store.NextSyncCommitee == nil && &update.NextSyncCommittee != nil && updateAttestedPeriod == storePeriod + + if update.AttestedHeader.Slot <= store.FinalizedHeader.Slot && !updateHasNextCommittee { + return ErrNotRelevant + } + + if &update.FinalizedHeader != nil && update.FinalityBranch != nil { + if !isFinalityProofValid(&update.AttestedHeader, &update.FinalizedHeader, update.FinalityBranch) { + return ErrInvalidFinalityProof + } + } else if &update.FinalizedHeader != nil { + return ErrInvalidFinalityProof + } + + if &update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil { + if !isNextCommitteeProofValid(&update.AttestedHeader, update.NextSyncCommittee, *update.NextSyncCommitteeBranch) { + return ErrInvalidNextSyncCommitteeProof + } + } else if &update.NextSyncCommittee != nil { + return ErrInvalidNextSyncCommitteeProof + } + + var syncCommittee *consensus_core.SyncCommittee + if updateSigPeriod == storePeriod { + syncCommittee = &in.Store.CurrentSyncCommitee + } else { + syncCommittee = in.Store.NextSyncCommitee + } + + pks, err := GetParticipatingKeys(syncCommittee, update.SyncAggregate.SyncCommitteeBits) + if err != nil { + return fmt.Errorf("failed to get participating keys: %w", err) + } + + forkVersion := CalculateForkVersion(&forks, update.SignatureSlot) + forkDataRoot := ComputeForkDataRoot(forkVersion, consensus_core.Bytes32(in.Config.Chain.GenesisRoot)) + + if !verifySyncCommitteeSignature(pks, &update.AttestedHeader, &update.SyncAggregate.SyncCommitteeSignature, forkDataRoot) { + return ErrInvalidSignature + } + + return nil + } +} +func (in *Inner) verify_update(update *consensus_core.Update) error { + genUpdate := GenericUpdate{ + AttestedHeader: update.AttestedHeader, + SyncAggregate: update.SyncAggregate, + SignatureSlot: update.SignatureSlot, + NextSyncCommittee: &update.NextSyncCommittee, + NextSyncCommitteeBranch: &update.NextSyncCommitteeBranch, + FinalizedHeader: update.FinalizedHeader, + FinalityBranch: update.FinalityBranch, + } + return in.verify_generic_update(&genUpdate, in.expected_current_slot(), &in.Store, in.Config.Chain.GenesisRoot, in.Config.Forks) +} +func (in *Inner) verify_finality_update(update *consensus_core.FinalityUpdate) error { + genUpdate := GenericUpdate{ + AttestedHeader: update.AttestedHeader, + SyncAggregate: update.SyncAggregate, + SignatureSlot: update.SignatureSlot, + FinalizedHeader: update.FinalizedHeader, + FinalityBranch: update.FinalityBranch, + } + return in.verify_generic_update(&genUpdate, in.expected_current_slot(), &in.Store, in.Config.Chain.GenesisRoot, in.Config.Forks) +} +func (in *Inner) verify_optimistic_update(update *consensus_core.OptimisticUpdate) error { + genUpdate := GenericUpdate{ + AttestedHeader: update.AttestedHeader, + SyncAggregate: update.SyncAggregate, + SignatureSlot: update.SignatureSlot, + } + return in.verify_generic_update(&genUpdate, in.expected_current_slot(), &in.Store, in.Config.Chain.GenesisRoot, in.Config.Forks) +} +func (in *Inner) apply_generic_update(store *LightClientStore, update *GenericUpdate) *[]byte { + committeeBits := getBits(update.SyncAggregate.SyncCommitteeBits) + + // Update max active participants + if committeeBits > store.CurrentMaxActiveParticipants { + store.CurrentMaxActiveParticipants = committeeBits + } + + // Determine if we should update the optimistic header + shouldUpdateOptimistic := committeeBits > in.safety_threshold() && + update.AttestedHeader.Slot > store.OptimisticHeader.Slot + + if shouldUpdateOptimistic { + store.OptimisticHeader = update.AttestedHeader + } + + updateAttestedPeriod := CalcSyncPeriod(update.AttestedHeader.Slot) + + updateFinalizedSlot := uint64(0) + if &update.FinalizedHeader != nil { + updateFinalizedSlot = update.FinalizedHeader.Slot + } + updateFinalizedPeriod := CalcSyncPeriod(updateFinalizedSlot) + + updateHasFinalizedNextCommittee := in.Store.NextSyncCommitee == nil && + in.has_sync_update(update) && in.has_finality_update(update) && + updateFinalizedPeriod == updateAttestedPeriod + + // Determine if we should apply the update + hasMajority := committeeBits*3 >= 512*2 + if !hasMajority { + log.Println("skipping block with low vote count") + } + + updateIsNewer := updateFinalizedSlot > store.FinalizedHeader.Slot + goodUpdate := updateIsNewer || updateHasFinalizedNextCommittee + + shouldApplyUpdate := hasMajority && goodUpdate + + // Apply the update if conditions are met + if shouldApplyUpdate { + storePeriod := CalcSyncPeriod(store.FinalizedHeader.Slot) + + // Sync committee update logic + if store.NextSyncCommitee == nil { + store.NextSyncCommitee = update.NextSyncCommittee + } else if updateFinalizedPeriod == storePeriod+1 { + log.Println("sync committee updated") + store.CurrentSyncCommitee = *store.NextSyncCommitee + store.NextSyncCommitee = update.NextSyncCommittee + store.PreviousMaxActiveParticipants = store.CurrentMaxActiveParticipants + store.CurrentMaxActiveParticipants = 0 + } + + // Update finalized header + if updateFinalizedSlot > store.FinalizedHeader.Slot { + store.FinalizedHeader = update.FinalizedHeader + + if store.FinalizedHeader.Slot > store.OptimisticHeader.Slot { + store.OptimisticHeader = store.FinalizedHeader + } + + if store.FinalizedHeader.Slot%32 == 0 { + checkpoint, err := TreeHashRoot(store.FinalizedHeader.ToBytes()) + if err != nil { + return nil + } + return &checkpoint + } + } + } + + return nil +} +func (in *Inner) apply_update(update *consensus_core.Update) { + genUpdate := GenericUpdate{ + AttestedHeader: update.AttestedHeader, + SyncAggregate: update.SyncAggregate, + SignatureSlot: update.SignatureSlot, + NextSyncCommittee: &update.NextSyncCommittee, + NextSyncCommitteeBranch: &update.NextSyncCommitteeBranch, + FinalizedHeader: update.FinalizedHeader, + FinalityBranch: update.FinalityBranch, + } + checkpoint := in.apply_generic_update(&in.Store, &genUpdate) + if checkpoint != nil { + in.lastCheckpoint = checkpoint + } +} +func (in *Inner) apply_finality_update(update *consensus_core.FinalityUpdate) { + genUpdate := GenericUpdate{ + AttestedHeader: update.AttestedHeader, + SyncAggregate: update.SyncAggregate, + SignatureSlot: update.SignatureSlot, + FinalizedHeader: update.FinalizedHeader, + FinalityBranch: update.FinalityBranch, + } + checkpoint := in.apply_generic_update(&in.Store, &genUpdate) + if checkpoint != nil { + in.lastCheckpoint = checkpoint + } +} +func (in *Inner) apply_optimistic_update(update *consensus_core.OptimisticUpdate) { + genUpdate := GenericUpdate{ + AttestedHeader: update.AttestedHeader, + SyncAggregate: update.SyncAggregate, + SignatureSlot: update.SignatureSlot, + } + checkpoint := in.apply_generic_update(&in.Store, &genUpdate) + if checkpoint != nil { + in.lastCheckpoint = checkpoint + } +} +func (in *Inner) log_finality_update(update *consensus_core.FinalityUpdate) { + participation := float32(getBits(update.SyncAggregate.SyncCommitteeBits)) / 512.0 * 100.0 + decimals := 2 + if participation == 100.0 { + decimals = 1 + } + + age := in.age(in.Store.FinalizedHeader.Slot) + days := age.Hours() / 24 + hours := int(age.Hours()) % 24 + minutes := int(age.Minutes()) % 60 + seconds := int(age.Seconds()) % 60 + + log.Printf( + "finalized slot slot=%d confidence=%.*f%% age=%02d:%02d:%02d:%02d", + in.Store.FinalizedHeader.Slot, decimals, participation, int(days), hours, minutes, seconds, + ) +} +func (in *Inner) log_optimistic_update(update *consensus_core.OptimisticUpdate) { + participation := float32(getBits(update.SyncAggregate.SyncCommitteeBits)) / 512.0 * 100.0 + decimals := 2 + if participation == 100.0 { + decimals = 1 + } + + age := in.age(in.Store.OptimisticHeader.Slot) + days := age.Hours() / 24 + hours := int(age.Hours()) % 24 + minutes := int(age.Minutes()) % 60 + seconds := int(age.Seconds()) % 60 + + log.Printf( + "updated head slot=%d confidence=%.*f%% age=%02d:%02d:%02d:%02d", + in.Store.OptimisticHeader.Slot, decimals, participation, int(days), hours, minutes, seconds, + ) +} +func (in *Inner) has_finality_update(update *GenericUpdate) bool { + return &update.FinalizedHeader != nil && update.FinalityBranch != nil +} +func (in *Inner) has_sync_update(update *GenericUpdate) bool { + return &update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil +} +func (in *Inner) safety_threshold() uint64 { + return max(in.Store.CurrentMaxActiveParticipants, in.Store.PreviousMaxActiveParticipants) / 2 +} + +// verifySyncCommitteeSignature verifies the sync committee signature. +func verifySyncCommitteeSignature( + pks []consensus_core.BLSPubKey, // Public keys slice + attestedHeader *consensus_core.Header, // Attested header + signature *consensus_core.SignatureBytes, // Signature bytes + forkDataRoot consensus_core.Bytes32, // Fork data root +) bool { + // Collect public keys as references (or suitable Go struct) + collectedPks := make([]*consensus_core.BLSPubKey, len(pks)) + for i := range pks { + collectedPks[i] = &pks[i] + } + + // Compute headerRoot + headerRoot, err := TreeHashRoot(attestedHeader.ToBytes()) + if err != nil { + return false + } + var headerRootBytes consensus_core.Bytes32 + copy(headerRootBytes[:], headerRoot[:]) + // Compute signingRoot + signingRoot := ComputeCommitteeSignRoot(headerRootBytes, forkDataRoot) + var g2Points []*bls.G2Point + for _, pk := range collectedPks { + var g2Point bls.G2Point + errWhileCoonvertingtoG2 := g2Point.Unmarshal(pk[:]) + + if errWhileCoonvertingtoG2 != nil { + return false + } + } + + return isAggregateValid(*signature, signingRoot, g2Points) +} + +func ComputeCommitteeSignRoot(header consensus_core.Bytes32, fork consensus_core.Bytes32) consensus_core.Bytes32 { + // Domain type for the sync committee + domainType := [4]byte{7, 0, 0, 0} + + // Compute the domain + domain := ComputeDomain(domainType, fork) + + // Compute and return the signing root + return ComputeSigningRoot(header, domain) +} +func (in *Inner) age(slot uint64) time.Duration { + expectedTime := slot*12 + in.Config.Chain.GenesisTime + now := time.Now().Unix() + return time.Duration(uint64(now)-expectedTime) * time.Second +} +func (in *Inner) expected_current_slot() uint64 { + const SLOT_DURATION = 12 + + now := time.Now().Unix() + + sinceGenesis := now - int64(in.Config.Chain.GenesisTime) + return uint64(sinceGenesis) / SLOT_DURATION +} +func (in *Inner) is_valid_checkpoint(blockHashSlot uint64) bool { + const SLOT_DURATION = 12 + currentSlot := in.expected_current_slot() + currentSlotTimestamp := int64(in.Config.Chain.GenesisTime) + int64(currentSlot)*SLOT_DURATION + blockhashSlotTimestamp := int64(in.Config.Chain.GenesisTime) + int64(blockHashSlot)*SLOT_DURATION + + slotAge := currentSlotTimestamp - blockhashSlotTimestamp + return uint64(slotAge) < in.Config.MaxCheckpointAge +} + +func isFinalityProofValid(attestedHeader *consensus_core.Header, finalizedHeader *consensus_core.Header, finalityBranch []consensus_core.Bytes32) bool { + finalityBranchForProof, err := branchToNodes(finalityBranch) + if err != nil { + return false + } + return isProofValid(attestedHeader, finalizedHeader.ToBytes(), finalityBranchForProof, 6, 41) +} + +func isCurrentCommitteeProofValid(attestedHeader *consensus_core.Header, currentCommittee *consensus_core.SyncCommittee, currentCommitteeBranch []consensus_core.Bytes32) bool { + CurrentCommitteeForProof, err := branchToNodes(currentCommitteeBranch) + if err != nil { + return false + } + return isProofValid(attestedHeader, currentCommittee.ToBytes(), CurrentCommitteeForProof, 5, 22) +} + +func isNextCommitteeProofValid(attestedHeader *consensus_core.Header, currentCommittee *consensus_core.SyncCommittee, currentCommitteeBranch []consensus_core.Bytes32) bool { + currentCommitteeBranchForProof, err := branchToNodes(currentCommitteeBranch) + if err != nil { + return false + } + return isProofValid(attestedHeader, currentCommittee.ToBytes(), currentCommitteeBranchForProof, 5, 23) +} + +func PayloadToBlock(value *consensus_core.ExecutionPayload) (*common.Block, error) { + emptyNonce := "0x0000000000000000" + emptyUncleHash := geth.HexToHash("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") + + // Allocate txs on the heap by using make with a predefined capacity + txs := make([]common.Transaction, 0, len(value.Transactions)) + + // Process each transaction + for i := range value.Transactions { + tx, err := processTransaction(&value.Transactions[i], value.BlockHash, &value.BlockNumber, uint64(i)) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + + // Construct and return the block + return &common.Block{ + Number: value.BlockNumber, + BaseFeePerGas: *uint256.NewInt(value.BaseFeePerGas), + Difficulty: *uint256.NewInt(0), + ExtraData: value.ExtraData[:], + GasLimit: value.GasLimit, + GasUsed: value.GasUsed, + Hash: value.BlockHash, + LogsBloom: value.LogsBloom[:], + ParentHash: value.ParentHash, + ReceiptsRoot: value.ReceiptsRoot, + StateRoot: value.StateRoot, + Timestamp: value.Timestamp, + TotalDifficulty: uint64(0), + Transactions: common.Transactions{Full: txs}, + MixHash: value.PrevRandao, + Nonce: emptyNonce, + Sha3Uncles: emptyUncleHash, + Size: 0, + TransactionsRoot: [32]byte{}, + Uncles: [][32]byte{}, + BlobGasUsed: value.BlobGasUsed, + ExcessBlobGas: value.ExcessBlobGas, + }, nil +} + +func processTransaction(txBytes *[1073741824]byte, blockHash consensus_core.Bytes32, blockNumber *uint64, index uint64) (common.Transaction, error) { + // Decode the transaction envelope (RLP-encoded) + + txEnvelope, err := DecodeTxEnvelope(txBytes) + if err != nil { + return common.Transaction{}, fmt.Errorf("failed to decode transaction: %v", err) + } + + tx := common.Transaction{ + Hash: txEnvelope.Hash(), + Nonce: txEnvelope.Nonce(), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: index, + To: txEnvelope.To(), + Value: txEnvelope.Value(), + GasPrice: txEnvelope.GasPrice(), + Gas: txEnvelope.Gas(), + Input: txEnvelope.Data(), + ChainID: txEnvelope.ChainId(), + TransactionType: txEnvelope.Type(), + } + + // Handle signature and transaction type logic + signer := types.LatestSignerForChainID(txEnvelope.ChainId()) + from, err := types.Sender(signer, txEnvelope) + if err != nil { + return common.Transaction{}, fmt.Errorf("failed to recover sender: %v", err) + } + tx.From = from.Hex() + + // Extract signature components + r, s, v := txEnvelope.RawSignatureValues() + tx.Signature = &common.Signature{ + R: r.String(), + S: s.String(), + V: v.Uint64(), + YParity: common.Parity{Value: v.Uint64() == 1}, + } + + switch txEnvelope.Type() { + case types.AccessListTxType: + tx.AccessList = txEnvelope.AccessList() + case types.DynamicFeeTxType: + tx.MaxFeePerGas = new(big.Int).Set(txEnvelope.GasFeeCap()) + tx.MaxPriorityFeePerGas = new(big.Int).Set(txEnvelope.GasTipCap()) + case types.BlobTxType: + tx.MaxFeePerGas = new(big.Int).Set(txEnvelope.GasFeeCap()) + tx.MaxPriorityFeePerGas = new(big.Int).Set(txEnvelope.GasTipCap()) + tx.MaxFeePerBlobGas = new(big.Int).Set(txEnvelope.BlobGasFeeCap()) + tx.BlobVersionedHashes = txEnvelope.BlobHashes() + default: + fmt.Println("Unhandled transaction type") + } + + return tx, nil +} + +// getBits counts the number of bits set to 1 in a [64]byte array +func getBits(bitfield [64]byte) uint64 { + var count uint64 + for _, b := range bitfield { + count += uint64(popCount(b)) + } + return count +} + +// popCount counts the number of bits set to 1 in a byte +func popCount(b byte) int { + count := 0 + for b != 0 { + count += int(b & 1) + b >>= 1 + } + return count +} + +// DecodeTxEnvelope takes the transaction bytes and decodes them into a transaction envelope (Ethereum transaction) +func DecodeTxEnvelope(txBytes *[1073741824]byte) (*types.Transaction, error) { + // Create an empty transaction object + var tx types.Transaction + + var txBytesForUnmarshal []byte + for _, b := range txBytes { + if b == 0 { + break + } + txBytesForUnmarshal = append(txBytesForUnmarshal, b) + } + + // Unmarshal the RLP-encoded transaction bytes into the transaction object + err := tx.UnmarshalBinary(txBytesForUnmarshal) + if err != nil { + return nil, fmt.Errorf("failed to decode transaction: %v", err) + } + + return &tx, nil +} + +func SomeGasPrice(gasFeeCap, gasTipCap *big.Int, baseFeePerGas uint64) *big.Int { + // Create a new big.Int for the base fee and set its value + baseFee := new(big.Int).SetUint64(baseFeePerGas) + + // Calculate the maximum gas price based on the provided parameters + maxGasPrice := new(big.Int).Set(gasFeeCap) + + // Calculate the alternative gas price + alternativeGasPrice := new(big.Int).Set(gasTipCap) + alternativeGasPrice.Add(alternativeGasPrice, baseFee) + + // Return the maximum of the two + if maxGasPrice.Cmp(alternativeGasPrice) < 0 { + return alternativeGasPrice + } + return maxGasPrice +} diff --git a/consensus/consensus_core/consensus_core.go b/consensus/consensus_core/consensus_core.go index bb6a8f9..df60f60 100644 --- a/consensus/consensus_core/consensus_core.go +++ b/consensus/consensus_core/consensus_core.go @@ -1,29 +1,42 @@ package consensus_core -type BeaconBlock struct { - Slot uint64 - Proposer_index uint64 - Parent_root [32]byte - StateRoot [32]byte - Body BeaconBlockBody -} + +import ( + "bytes" + + "github.com/BlocSoc-iitr/selene/consensus/types" + "github.com/ugorji/go/codec" +) + type Bytes32 [32]byte type BLSPubKey [48]byte type Address [20]byte -type LogsBloom = [256]byte +type LogsBloom [256]byte type SignatureBytes [96]byte + +type BeaconBlock struct { + Slot uint64 + ProposerIndex uint64 + ParentRoot Bytes32 + StateRoot Bytes32 + Body BeaconBlockBody +} + type Eth1Data struct { - Deposit_root Bytes32 - Deposit_count uint64 - Block_hash Bytes32 + DepositRoot Bytes32 + DepositCount uint64 + BlockHash Bytes32 } + type ProposerSlashing struct { SignedHeader1 SignedBeaconBlockHeader SignedHeader2 SignedBeaconBlockHeader } + type SignedBeaconBlockHeader struct { Message BeaconBlockHeader Signature SignatureBytes } + type BeaconBlockHeader struct { Slot uint64 ProposerIndex uint64 @@ -31,15 +44,18 @@ type BeaconBlockHeader struct { StateRoot Bytes32 BodyRoot Bytes32 } + type AttesterSlashing struct { Attestation1 IndexedAttestation Attestation2 IndexedAttestation } + type IndexedAttestation struct { - AttestingIndices []uint64 //max length 2048 to be ensured + AttestingIndices [2048]uint64 Data AttestationData Signature SignatureBytes } + type AttestationData struct { Slot uint64 Index uint64 @@ -47,45 +63,55 @@ type AttestationData struct { Source Checkpoint Target Checkpoint } + type Checkpoint struct { Epoch uint64 Root Bytes32 } + type Bitlist []bool + type Attestation struct { AggregationBits Bitlist `ssz-max:"2048"` Data AttestationData Signature SignatureBytes } + type Deposit struct { - Proof [33]Bytes32 //fixed size array + Proof [33]Bytes32 // fixed size array Data DepositData } + type DepositData struct { Pubkey [48]byte WithdrawalCredentials Bytes32 Amount uint64 Signature SignatureBytes } + type SignedVoluntaryExit struct { Message VoluntaryExit Signature SignatureBytes } + type VoluntaryExit struct { Epoch uint64 ValidatorIndex uint64 } + type SyncAggregate struct { SyncCommitteeBits [64]byte SyncCommitteeSignature SignatureBytes } + type Withdrawal struct { Index uint64 ValidatorIndex uint64 Address Address Amount uint64 } -type ExecutionPayload struct { //not implemented + +type ExecutionPayload struct { ParentHash Bytes32 FeeRecipient Address StateRoot Bytes32 @@ -99,33 +125,38 @@ type ExecutionPayload struct { //not implemented ExtraData [32]byte BaseFeePerGas uint64 BlockHash Bytes32 - Transactions []byte //max length 1073741824 to be implemented - Withdrawals []Withdrawal //max length 16 to be implemented - BlobGasUsed uint64 - ExcessBlobGas uint64 + Transactions []types.Transaction `ssz-max:"1048576"` + Withdrawals []types.Withdrawal `ssz-max:"16"` + BlobGasUsed *uint64 // Deneb-specific field, use pointer for optionality + ExcessBlobGas *uint64 // Deneb-specific field, use pointer for optionality } + type SignedBlsToExecutionChange struct { Message BlsToExecutionChange Signature SignatureBytes } + type BlsToExecutionChange struct { ValidatorIndex uint64 FromBlsPubkey [48]byte } -type BeaconBlockBody struct { //not implemented + +// BeaconBlockBody represents the body of a beacon block. +type BeaconBlockBody struct { RandaoReveal SignatureBytes Eth1Data Eth1Data Graffiti Bytes32 - ProposerSlashings []ProposerSlashing //max length 16 to be insured how? - AttesterSlashings []AttesterSlashing //max length 2 to be ensured - Attestations []Attestation //max length 128 to be ensured - Deposits []Deposit //max length 16 to be ensured - VoluntaryExits SignedVoluntaryExit + ProposerSlashings []ProposerSlashing `ssz-max:"16"` + AttesterSlashings []AttesterSlashing `ssz-max:"2"` + Attestations []Attestation `ssz-max:"128"` + Deposits []Deposit `ssz-max:"16"` + VoluntaryExits []SignedVoluntaryExit `ssz-max:"16"` SyncAggregate SyncAggregate ExecutionPayload ExecutionPayload - BlsToExecutionChanges []SignedBlsToExecutionChange //max length 16 to be ensured - BlobKzgCommitments [][48]byte //max length 4096 to be ensured + BlsToExecutionChanges []SignedBlsToExecutionChange `ssz-max:"16"` + BlobKzgCommitments [][48]byte `ssz-max:"4096"` } + type Header struct { Slot uint64 ProposerIndex uint64 @@ -133,19 +164,22 @@ type Header struct { StateRoot Bytes32 BodyRoot Bytes32 } -type SyncComittee struct { + +type SyncCommittee struct { Pubkeys [512]BLSPubKey AggregatePubkey BLSPubKey } + type Update struct { AttestedHeader Header - NextSyncCommittee SyncComittee + NextSyncCommittee SyncCommittee NextSyncCommitteeBranch []Bytes32 FinalizedHeader Header - SinalityBranch []Bytes32 + FinalityBranch []Bytes32 SyncAggregate SyncAggregate SignatureSlot uint64 } + type FinalityUpdate struct { AttestedHeader Header FinalizedHeader Header @@ -153,13 +187,38 @@ type FinalityUpdate struct { SyncAggregate SyncAggregate SignatureSlot uint64 } + type OptimisticUpdate struct { AttestedHeader Header SyncAggregate SyncAggregate SignatureSlot uint64 } + type Bootstrap struct { Header Header - CurrentSyncCommittee SyncComittee + CurrentSyncCommittee SyncCommittee CurrentSyncCommitteeBranch []Bytes32 } + +// ToBytes serializes the Header struct to a byte slice. +func (h *Header) ToBytes() []byte { + var buf bytes.Buffer + enc := codec.NewEncoder(&buf, new(codec.JsonHandle)) + _ = enc.Encode(h) // Ignore error + return buf.Bytes() +} + +func (b *BeaconBlockBody) ToBytes() []byte { + var buf bytes.Buffer + enc := codec.NewEncoder(&buf, new(codec.JsonHandle)) + _ = enc.Encode(b) // Ignore error + return buf.Bytes() +} + +// ToBytes serializes the SyncCommittee struct to a byte slice. +func (sc *SyncCommittee) ToBytes() []byte { + var buf bytes.Buffer + enc := codec.NewEncoder(&buf, new(codec.JsonHandle)) + _ = enc.Encode(sc) // Ignore error + return buf.Bytes() +} diff --git a/consensus/rpc/consensus_rpc.go b/consensus/rpc/consensus_rpc.go index 8395c79..22dc00e 100644 --- a/consensus/rpc/consensus_rpc.go +++ b/consensus/rpc/consensus_rpc.go @@ -6,7 +6,7 @@ import ( // return types not mention and oarameters as well type ConsensusRpc interface { - GetBootstrap(block_root []byte) (consensus_core.Bootstrap, error) + GetBootstrap(block_root [32]byte) (consensus_core.Bootstrap, error) GetUpdates(period uint64, count uint8) ([]consensus_core.Update, error) GetFinalityUpdate() (consensus_core.FinalityUpdate, error) GetOptimisticUpdate() (consensus_core.OptimisticUpdate, error) diff --git a/consensus/rpc/nimbus_rpc.go b/consensus/rpc/nimbus_rpc.go index 1725b4e..49b66fa 100644 --- a/consensus/rpc/nimbus_rpc.go +++ b/consensus/rpc/nimbus_rpc.go @@ -3,11 +3,12 @@ package rpc import ( "encoding/json" "fmt" - "github.com/BlocSoc-iitr/selene/consensus/consensus_core" "io" "net/http" "strconv" "time" + + "github.com/BlocSoc-iitr/selene/consensus/consensus_core" ) // uses types package @@ -43,15 +44,17 @@ func min(a uint8, b uint8) uint8 { } return b } + type NimbusRpc struct { //ConsensusRpc rpc string } + func NewNimbusRpc(rpc string) *NimbusRpc { return &NimbusRpc{ rpc: rpc} } -func (n *NimbusRpc) GetBootstrap(block_root []byte) (consensus_core.Bootstrap, error) { +func (n *NimbusRpc) GetBootstrap(block_root [32]byte) (consensus_core.Bootstrap, error) { root_hex := fmt.Sprintf("%x", block_root) req := fmt.Sprintf("%s/eth/v1/beacon/light_client/bootstrap/0x%s", n.rpc, root_hex) var res BootstrapResponse @@ -111,6 +114,7 @@ func (n *NimbusRpc) ChainId() (uint64, error) { } return res.Data.ChainId, nil } + // BeaconBlock, Update,FinalityUpdate ,OptimisticUpdate,Bootstrap yet to be defined in consensus-core/src/types/mod.go // For now defined in consensus/consensus_core.go type BeaconBlockResponse struct { @@ -138,4 +142,3 @@ type Spec struct { type BootstrapResponse struct { Data consensus_core.Bootstrap } - diff --git a/consensus/utils.go b/consensus/utils.go index 4e25578..6399968 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -1,15 +1,244 @@ package consensus -// uses types package - -func calculate_sync_period() {} -func is_aggregate_valid() {} -func is_proof_valid() {} -func compute_signing_root() {} -func compute_domian() {} -func compute_fork_data_root() {} -func branch_to_nodes() {} -func bytes32_to_nodes() {} - -type SigningData struct{} -type ForkData struct{} +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + "log" + "math" + + "github.com/BlocSoc-iitr/selene/config" + "github.com/BlocSoc-iitr/selene/consensus/consensus_core" + "github.com/BlocSoc-iitr/selene/utils/bls" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/ethereum/go-ethereum/crypto" +) + +// TreeHashRoot computes the Merkle root from the provided leaves in a flat []byte slice. +func TreeHashRoot(data []byte) ([]byte, error) { + // Convert the input data into a slice of leaves + leaves, err := bytesToLeaves(data) + if err != nil { + return nil, err + } + + nodes := leaves // Start with the leaf nodes + + for len(nodes) > 1 { + var newLevel [][]byte + + // Pair nodes and hash them + for i := 0; i < len(nodes); i += 2 { + if i+1 < len(nodes) { + // Hash pair of nodes + nodeHash := crypto.Keccak256(append(nodes[i], nodes[i+1]...)) + newLevel = append(newLevel, nodeHash) + } else { + // Handle odd number of nodes (carry last node up) + newLevel = append(newLevel, nodes[i]) + } + } + + nodes = newLevel + } + + // Return the root hash + return nodes[0], nil +} + +func bytesToLeaves(data []byte) ([][]byte, error) { + var leaves [][]byte + if err := json.Unmarshal(data, &leaves); err != nil { + return nil, err + } + + return leaves, nil +} + +func CalcSyncPeriod(slot uint64) uint64 { + epoch := slot / 32 + return epoch / 256 +} + +// isAggregateValid checks if the provided signature is valid for the given message and public keys. +func isAggregateValid(sigBytes consensus_core.SignatureBytes, msg [32]byte, pks []*bls.G2Point) bool { + var sigInBytes [96]byte + copy(sigInBytes[:], sigBytes[:]) + // Deserialize the signature from bytes + var sig bls12381.G1Affine + if err := sig.Unmarshal(sigInBytes[:]); err != nil { + return false + } + + // Map the message to a point on the curve + msgPoint := bls.MapToCurve(msg) + + // Aggregate the public keys + aggPubKey := bls.AggregatePublicKeys(pks) + + // Prepare the pairing check inputs + P := [2]bls12381.G1Affine{*msgPoint, sig} + Q := [2]bls12381.G2Affine{*aggPubKey.G2Affine, *bls.GetG2Generator()} + + // Perform the pairing check + ok, err := bls12381.PairingCheck(P[:], Q[:]) + if err != nil { + return false + } + return ok +} + +func isProofValid( + attestedHeader *consensus_core.Header, + leafObject []byte, // Single byte slice of the leaf object + branch [][]byte, // Slice of byte slices for the branch + depth, index int, // Depth of the Merkle proof and index of the leaf +) bool { + // If the branch length is not equal to the depth, return false + if len(branch) != depth { + return false + } + + // Compute the root hash of the leaf object + derivedRoot, err := TreeHashRoot(leafObject) + if err != nil { + return false + } + + // Iterate through the branch and compute the Merkle root + for i, node := range branch { + hasher := sha256.New() + + // Check if index / 2^i is odd or even + if (index/int(math.Pow(2, float64(i))))%2 != 0 { + // If odd, hash(node || derived_root) + hasher.Write(node) + hasher.Write(derivedRoot[:]) + } else { + // If even, hash(derived_root || node) + hasher.Write(derivedRoot[:]) + hasher.Write(node) + } + + // Update the derived root + derivedRootNew := sha256.Sum256(hasher.Sum(nil)) + derivedRoot = derivedRootNew[:] + } + + // Compare the final derived root with the attested header's state root + return bytes.Equal(derivedRoot[:], attestedHeader.StateRoot[:]) +} + +func CalculateForkVersion(forks *config.Forks, slot uint64) [4]byte { + epoch := slot / 32 + + switch { + case epoch >= forks.Deneb.Epoch: + return [4]byte(forks.Deneb.ForkVersion) + case epoch >= forks.Capella.Epoch: + return [4]byte(forks.Capella.ForkVersion) + case epoch >= forks.Bellatrix.Epoch: + return [4]byte(forks.Bellatrix.ForkVersion) + case epoch >= forks.Altair.Epoch: + return [4]byte(forks.Altair.ForkVersion) + default: + return [4]byte(forks.Genesis.ForkVersion) + } +} + +func ComputeForkDataRoot(currentVersion [4]byte, genesisValidatorRoot consensus_core.Bytes32) consensus_core.Bytes32 { + forkData := ForkData{ + CurrentVersion: currentVersion, + GenesisValidatorRoot: genesisValidatorRoot, + } + + hash, err := TreeHashRoot(forkData.ToBytes()) + if err != nil { + return consensus_core.Bytes32{} + } + return consensus_core.Bytes32(hash) +} + +// GetParticipatingKeys retrieves the participating public keys from the committee based on the bitfield represented as a byte array. +func GetParticipatingKeys(committee *consensus_core.SyncCommittee, bitfield [64]byte) ([]consensus_core.BLSPubKey, error) { + var pks []consensus_core.BLSPubKey + numBits := len(bitfield) * 8 // Total number of bits + + if len(committee.Pubkeys) > numBits { + return nil, fmt.Errorf("bitfield is too short for the number of public keys") + } + + for i := 0; i < len(bitfield); i++ { + byteVal := bitfield[i] + for bit := 0; bit < 8; bit++ { + if (byteVal & (1 << bit)) != 0 { + index := i*8 + bit + if index >= len(committee.Pubkeys) { + break + } + pks = append(pks, committee.Pubkeys[index]) + } + } + } + + return pks, nil +} + +func ComputeSigningRoot(objectRoot, domain consensus_core.Bytes32) consensus_core.Bytes32 { + signingData := SigningData{ + ObjectRoot: objectRoot, + Domain: domain, + } + hash, err := TreeHashRoot(signingData.ToBytes()) + if err != nil { + return consensus_core.Bytes32{} + } + return consensus_core.Bytes32(hash) +} + +func ComputeDomain(domainType [4]byte, forkDataRoot consensus_core.Bytes32) consensus_core.Bytes32 { + data := append(domainType[:], forkDataRoot[:28]...) + return sha256.Sum256(data) +} + +type SigningData struct { + ObjectRoot consensus_core.Bytes32 + Domain consensus_core.Bytes32 +} + +type ForkData struct { + CurrentVersion [4]byte + GenesisValidatorRoot consensus_core.Bytes32 +} + +func (fd *ForkData) ToBytes() []byte { + data, err := json.Marshal(fd) + if err != nil { + log.Println("Error marshaling ForkData:", err) + return nil // Or return an empty slice, based on your preference + } + return data +} + +func (sd *SigningData) ToBytes() []byte { + data, err := json.Marshal(sd) + if err != nil { + log.Println("Error marshaling SigningData:", err) + return nil // Or return an empty slice, based on your preference + } + return data +} + +func bytes32ToNode(bytes consensus_core.Bytes32) []byte { + return []byte(bytes[:]) +} + +// branchToNodes converts a slice of Bytes32 to a slice of Node +func branchToNodes(branch []consensus_core.Bytes32) ([][]byte, error) { + nodes := make([][]byte, len(branch)) + for i, b32 := range branch { + nodes[i] = bytes32ToNode(b32) + } + return nodes, nil +} diff --git a/go.mod b/go.mod index ddc3288..ca4368d 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.11 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect github.com/wealdtech/go-merkletree v1.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect diff --git a/go.sum b/go.sum index 75d9c8c..661a12d 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/wealdtech/go-merkletree v1.0.0 h1:DsF1xMzj5rK3pSQM6mPv8jlyJyHXhFxpnA2bwEjMMBY= github.com/wealdtech/go-merkletree v1.0.0/go.mod h1:cdil512d/8ZC7Kx3bfrDvGMQXB25NTKbsm0rFrmDax4= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= From ff98e73825223d92f0ffc8ea4c6dc6cd6a6ebee9 Mon Sep 17 00:00:00 2001 From: DarkLord017 Date: Sat, 21 Sep 2024 15:11:58 +0530 Subject: [PATCH 2/9] Edited logging mistake in consensus.go --- consensus/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index 28453b8..1a90e86 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -320,7 +320,7 @@ func (in *Inner) get_payloads(ctx context.Context, startSlot, endSlot uint64) ([ } payload := block.Body.ExecutionPayload if payload.ParentHash != prevParentHash { - log.Printf("Error while backfilling blocks: expected block hash %v but got %v", prevParentHash) + log.Printf("Error while backfilling blocks: expected block hash %v but got %v", prevParentHash, payload.ParentHash) return } prevParentHash = *&payload.ParentHash From 48fde326407397483386d6698af5e1d88ca0190e Mon Sep 17 00:00:00 2001 From: DarkLord017 Date: Sat, 21 Sep 2024 16:49:27 +0530 Subject: [PATCH 3/9] Fixed linter errors in consensus --- consensus/consensus.go | 50 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index 1a90e86..a4fc9b6 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -108,7 +108,7 @@ func (con ConsensusClient) New(rpc *string, config config.Config) ConsensusClien log.Printf("error while loading checkpoint: %v", errorWhileLoadingCheckpoint) } } - if &initialCheckpoint == nil { + if initialCheckpoint == [32]byte{} { panic("No checkpoint found") } In := &Inner{} @@ -177,7 +177,7 @@ func (con ConsensusClient) Shutdown() error { } return nil } -func (con ConsensusClient) expected_current_slot() uint64 { +func (con ConsensusClient) Expected_current_slot() uint64 { now := time.Now().Unix() // Assuming SLOT_DURATION is the duration of each slot in seconds const SLOT_DURATION uint64 = 12 @@ -195,11 +195,14 @@ func sync_fallback(inner *Inner, fallback *string) error { func sync_all_fallback(inner *Inner, chainID uint64) error { var n config.Network network, err := n.ChainID(chainID) + if err != nil { + return err + } ch := checkpoints.CheckpointFallback{} - checkpointFallback, err := ch.Build() - if err != nil { + checkpointFallback, errWhileCheckpoint := ch.Build() + if errWhileCheckpoint != nil { return err } @@ -292,7 +295,7 @@ func (in *Inner) check_execution_payload(ctx context.Context, slot *uint64) (*co return &payload, nil } -func (in *Inner) get_payloads(ctx context.Context, startSlot, endSlot uint64) ([]interface{}, error) { +func (in *Inner) Get_payloads(ctx context.Context, startSlot, endSlot uint64) ([]interface{}, error) { var payloads []interface{} // Fetch the block at endSlot to get the initial parent hash @@ -323,8 +326,8 @@ func (in *Inner) get_payloads(ctx context.Context, startSlot, endSlot uint64) ([ log.Printf("Error while backfilling blocks: expected block hash %v but got %v", prevParentHash, payload.ParentHash) return } - prevParentHash = *&payload.ParentHash - payloadsChan <- *&payload + prevParentHash = payload.ParentHash + payloadsChan <- payload }(slot) } @@ -426,6 +429,7 @@ func (in *Inner) sync(checkpoint [32]byte) error { in.apply_finality_update(&finalityUpdate) // Fetch and apply optimistic update + optimisticUpdate, err := in.RPC.GetOptimisticUpdate() if err != nil { return err @@ -502,7 +506,7 @@ func (in *Inner) bootstrap(checkpoint [32]byte) { isValid := in.is_valid_checkpoint(bootstrap.Header.Slot) if !isValid { - if in.Config.StrictCheckpointAge == true { + if in.Config.StrictCheckpointAge { log.Printf("checkpoint too old, consider using a more recent checkpoint") return } else { @@ -576,35 +580,37 @@ func (in *Inner) verify_generic_update(update *GenericUpdate, expectedCurrentSlo } updateAttestedPeriod := CalcSyncPeriod(update.AttestedHeader.Slot) - updateHasNextCommittee := store.NextSyncCommitee == nil && &update.NextSyncCommittee != nil && updateAttestedPeriod == storePeriod + updateHasNextCommittee := store.NextSyncCommitee == nil && update.NextSyncCommittee != nil && updateAttestedPeriod == storePeriod if update.AttestedHeader.Slot <= store.FinalizedHeader.Slot && !updateHasNextCommittee { return ErrNotRelevant } - if &update.FinalizedHeader != nil && update.FinalityBranch != nil { + // Validate finalized header and finality branch + if update.FinalizedHeader != (consensus_core.Header{}) && update.FinalityBranch != nil { if !isFinalityProofValid(&update.AttestedHeader, &update.FinalizedHeader, update.FinalityBranch) { return ErrInvalidFinalityProof } - } else if &update.FinalizedHeader != nil { + } else if update.FinalizedHeader != (consensus_core.Header{}) { return ErrInvalidFinalityProof } - if &update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil { + // Validate next sync committee and its branch + if update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil { if !isNextCommitteeProofValid(&update.AttestedHeader, update.NextSyncCommittee, *update.NextSyncCommitteeBranch) { return ErrInvalidNextSyncCommitteeProof } - } else if &update.NextSyncCommittee != nil { + } else if update.NextSyncCommittee != nil { return ErrInvalidNextSyncCommitteeProof } + // Set sync committee based on updateSigPeriod var syncCommittee *consensus_core.SyncCommittee if updateSigPeriod == storePeriod { syncCommittee = &in.Store.CurrentSyncCommitee } else { syncCommittee = in.Store.NextSyncCommitee } - pks, err := GetParticipatingKeys(syncCommittee, update.SyncAggregate.SyncCommitteeBits) if err != nil { return fmt.Errorf("failed to get participating keys: %w", err) @@ -669,7 +675,7 @@ func (in *Inner) apply_generic_update(store *LightClientStore, update *GenericUp updateAttestedPeriod := CalcSyncPeriod(update.AttestedHeader.Slot) updateFinalizedSlot := uint64(0) - if &update.FinalizedHeader != nil { + if update.FinalizedHeader != (consensus_core.Header{}) { updateFinalizedSlot = update.FinalizedHeader.Slot } updateFinalizedPeriod := CalcSyncPeriod(updateFinalizedSlot) @@ -763,14 +769,14 @@ func (in *Inner) apply_optimistic_update(update *consensus_core.OptimisticUpdate in.lastCheckpoint = checkpoint } } -func (in *Inner) log_finality_update(update *consensus_core.FinalityUpdate) { +func (in *Inner) Log_finality_update(update *consensus_core.FinalityUpdate) { participation := float32(getBits(update.SyncAggregate.SyncCommitteeBits)) / 512.0 * 100.0 decimals := 2 if participation == 100.0 { decimals = 1 } - age := in.age(in.Store.FinalizedHeader.Slot) + age := in.Age(in.Store.FinalizedHeader.Slot) days := age.Hours() / 24 hours := int(age.Hours()) % 24 minutes := int(age.Minutes()) % 60 @@ -781,14 +787,14 @@ func (in *Inner) log_finality_update(update *consensus_core.FinalityUpdate) { in.Store.FinalizedHeader.Slot, decimals, participation, int(days), hours, minutes, seconds, ) } -func (in *Inner) log_optimistic_update(update *consensus_core.OptimisticUpdate) { +func (in *Inner) Log_optimistic_update(update *consensus_core.OptimisticUpdate) { participation := float32(getBits(update.SyncAggregate.SyncCommitteeBits)) / 512.0 * 100.0 decimals := 2 if participation == 100.0 { decimals = 1 } - age := in.age(in.Store.OptimisticHeader.Slot) + age := in.Age(in.Store.OptimisticHeader.Slot) days := age.Hours() / 24 hours := int(age.Hours()) % 24 minutes := int(age.Minutes()) % 60 @@ -800,10 +806,10 @@ func (in *Inner) log_optimistic_update(update *consensus_core.OptimisticUpdate) ) } func (in *Inner) has_finality_update(update *GenericUpdate) bool { - return &update.FinalizedHeader != nil && update.FinalityBranch != nil + return update.FinalizedHeader != (consensus_core.Header{}) && update.FinalityBranch != nil } func (in *Inner) has_sync_update(update *GenericUpdate) bool { - return &update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil + return update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil } func (in *Inner) safety_threshold() uint64 { return max(in.Store.CurrentMaxActiveParticipants, in.Store.PreviousMaxActiveParticipants) / 2 @@ -854,7 +860,7 @@ func ComputeCommitteeSignRoot(header consensus_core.Bytes32, fork consensus_core // Compute and return the signing root return ComputeSigningRoot(header, domain) } -func (in *Inner) age(slot uint64) time.Duration { +func (in *Inner) Age(slot uint64) time.Duration { expectedTime := slot*12 + in.Config.Chain.GenesisTime now := time.Now().Unix() return time.Duration(uint64(now)-expectedTime) * time.Second From f42e6b46921b2a3afdf190e376ee0dcf706e256b Mon Sep 17 00:00:00 2001 From: DarkLord017 Date: Sat, 21 Sep 2024 18:01:57 +0530 Subject: [PATCH 4/9] Used merkle tree library for utils --- consensus/utils.go | 74 +++++++++++++++++--------------------- utils/proof/merkleProof.go | 2 +- utils/utils.go | 64 +++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 utils/utils.go diff --git a/consensus/utils.go b/consensus/utils.go index 6399968..1c0727f 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -6,16 +6,18 @@ import ( "encoding/json" "fmt" "log" - "math" "github.com/BlocSoc-iitr/selene/config" "github.com/BlocSoc-iitr/selene/consensus/consensus_core" "github.com/BlocSoc-iitr/selene/utils/bls" + utilsProof "github.com/BlocSoc-iitr/selene/utils/proof" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" - "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" + merkletree "github.com/wealdtech/go-merkletree" ) // TreeHashRoot computes the Merkle root from the provided leaves in a flat []byte slice. +// TreeHashRoot calculates the root hash from the input data. func TreeHashRoot(data []byte) ([]byte, error) { // Convert the input data into a slice of leaves leaves, err := bytesToLeaves(data) @@ -23,28 +25,19 @@ func TreeHashRoot(data []byte) ([]byte, error) { return nil, err } - nodes := leaves // Start with the leaf nodes - - for len(nodes) > 1 { - var newLevel [][]byte - - // Pair nodes and hash them - for i := 0; i < len(nodes); i += 2 { - if i+1 < len(nodes) { - // Hash pair of nodes - nodeHash := crypto.Keccak256(append(nodes[i], nodes[i+1]...)) - newLevel = append(newLevel, nodeHash) - } else { - // Handle odd number of nodes (carry last node up) - newLevel = append(newLevel, nodes[i]) - } - } + // Create the Merkle tree using the leaves + tree, err := merkletree.New(leaves) + if err != nil { + return nil, err + } - nodes = newLevel + // Fetch the root hash of the tree + root := tree.Root() + if root == nil { + return nil, errors.New("failed to calculate the Merkle root") } - // Return the root hash - return nodes[0], nil + return root, nil } func bytesToLeaves(data []byte) ([][]byte, error) { @@ -100,36 +93,33 @@ func isProofValid( return false } - // Compute the root hash of the leaf object - derivedRoot, err := TreeHashRoot(leafObject) + // Create the tree from the branch data + tree, err := merkletree.New(branch) if err != nil { + fmt.Printf("Error creating Merkle tree: %v", err) return false } - // Iterate through the branch and compute the Merkle root - for i, node := range branch { - hasher := sha256.New() + // Fetch the root hash of the tree + root := tree.Root() - // Check if index / 2^i is odd or even - if (index/int(math.Pow(2, float64(i))))%2 != 0 { - // If odd, hash(node || derived_root) - hasher.Write(node) - hasher.Write(derivedRoot[:]) - } else { - // If even, hash(derived_root || node) - hasher.Write(derivedRoot[:]) - hasher.Write(node) - } + // Validate the Merkle proof by generating it for the leaf object + proof, err := tree.GenerateProof(leafObject) + if err != nil { + fmt.Printf("Error generating proof: %v", err) + return false + } - // Update the derived root - derivedRootNew := sha256.Sum256(hasher.Sum(nil)) - derivedRoot = derivedRootNew[:] + // Validate the Merkle proof + isValid, err := utilsProof.ValidateMerkleProof(root, leafObject, proof) + if err != nil { + fmt.Printf("Error in validating Merkle proof: %v", err) + return false } - // Compare the final derived root with the attested header's state root - return bytes.Equal(derivedRoot[:], attestedHeader.StateRoot[:]) + // Verify against the attested header's state root + return isValid && bytes.Equal(root, attestedHeader.StateRoot[:]) } - func CalculateForkVersion(forks *config.Forks, slot uint64) [4]byte { epoch := slot / 32 diff --git a/utils/proof/merkleProof.go b/utils/proof/merkleProof.go index 3610c11..b28bc54 100644 --- a/utils/proof/merkleProof.go +++ b/utils/proof/merkleProof.go @@ -6,7 +6,7 @@ import ( merkletree "github.com/wealdtech/go-merkletree" ) -func validateMerkleProof( +func ValidateMerkleProof( root []byte, leaf []byte, proof *merkletree.Proof, diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..e68f30d --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,64 @@ +package utils + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/BlocSoc-iitr/selene/common" +) + +// if we need to export the functions , just make their first letter capitalised +func Hex_str_to_bytes(s string) ([]byte, error) { + s = strings.TrimPrefix(s, "0x") + + bytesArray, err := hex.DecodeString(s) + + if err != nil { + return nil, err + } + + return bytesArray, nil +} + +func Address_to_hex_string(addr common.Address) string { + bytesArray := addr.Bytes() + return fmt.Sprintf("0x%x", hex.EncodeToString(bytesArray)) +} + +func U64_to_hex_string(val uint64) string { + return fmt.Sprintf("0x%x", val) +} + +func BytesSerialise(bytes []byte) ([]byte, error) { + + if bytes == nil { + return json.Marshal(nil) + } + bytesString := hex.EncodeToString(bytes) + result, err := json.Marshal(bytesString) + if err != nil { + return nil, err + } + return result, nil + +} + +func BytesDeserialise(data []byte) ([]byte, error) { + var bytesOpt *string + if err := json.Unmarshal(data, &bytesOpt); err != nil { + return nil, err + } + + if bytesOpt == nil { + return nil, nil + } else { + bytes, err := common.Hex_str_to_bytes(*bytesOpt) + if err != nil { + return nil, err + } + return bytes, nil + } + +} From 1669600108c5f12ce99640767cd96a5f046d29cd Mon Sep 17 00:00:00 2001 From: DarkLord017 Date: Sat, 21 Sep 2024 18:08:33 +0530 Subject: [PATCH 5/9] Revert "Used merkle tree library for utils" This reverts commit f42e6b46921b2a3afdf190e376ee0dcf706e256b. --- consensus/utils.go | 74 +++++++++++++++++++++----------------- utils/proof/merkleProof.go | 2 +- utils/utils.go | 64 --------------------------------- 3 files changed, 43 insertions(+), 97 deletions(-) delete mode 100644 utils/utils.go diff --git a/consensus/utils.go b/consensus/utils.go index 1c0727f..6399968 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -6,18 +6,16 @@ import ( "encoding/json" "fmt" "log" + "math" "github.com/BlocSoc-iitr/selene/config" "github.com/BlocSoc-iitr/selene/consensus/consensus_core" "github.com/BlocSoc-iitr/selene/utils/bls" - utilsProof "github.com/BlocSoc-iitr/selene/utils/proof" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" - "github.com/pkg/errors" - merkletree "github.com/wealdtech/go-merkletree" + "github.com/ethereum/go-ethereum/crypto" ) // TreeHashRoot computes the Merkle root from the provided leaves in a flat []byte slice. -// TreeHashRoot calculates the root hash from the input data. func TreeHashRoot(data []byte) ([]byte, error) { // Convert the input data into a slice of leaves leaves, err := bytesToLeaves(data) @@ -25,19 +23,28 @@ func TreeHashRoot(data []byte) ([]byte, error) { return nil, err } - // Create the Merkle tree using the leaves - tree, err := merkletree.New(leaves) - if err != nil { - return nil, err - } + nodes := leaves // Start with the leaf nodes + + for len(nodes) > 1 { + var newLevel [][]byte + + // Pair nodes and hash them + for i := 0; i < len(nodes); i += 2 { + if i+1 < len(nodes) { + // Hash pair of nodes + nodeHash := crypto.Keccak256(append(nodes[i], nodes[i+1]...)) + newLevel = append(newLevel, nodeHash) + } else { + // Handle odd number of nodes (carry last node up) + newLevel = append(newLevel, nodes[i]) + } + } - // Fetch the root hash of the tree - root := tree.Root() - if root == nil { - return nil, errors.New("failed to calculate the Merkle root") + nodes = newLevel } - return root, nil + // Return the root hash + return nodes[0], nil } func bytesToLeaves(data []byte) ([][]byte, error) { @@ -93,33 +100,36 @@ func isProofValid( return false } - // Create the tree from the branch data - tree, err := merkletree.New(branch) + // Compute the root hash of the leaf object + derivedRoot, err := TreeHashRoot(leafObject) if err != nil { - fmt.Printf("Error creating Merkle tree: %v", err) return false } - // Fetch the root hash of the tree - root := tree.Root() + // Iterate through the branch and compute the Merkle root + for i, node := range branch { + hasher := sha256.New() - // Validate the Merkle proof by generating it for the leaf object - proof, err := tree.GenerateProof(leafObject) - if err != nil { - fmt.Printf("Error generating proof: %v", err) - return false - } + // Check if index / 2^i is odd or even + if (index/int(math.Pow(2, float64(i))))%2 != 0 { + // If odd, hash(node || derived_root) + hasher.Write(node) + hasher.Write(derivedRoot[:]) + } else { + // If even, hash(derived_root || node) + hasher.Write(derivedRoot[:]) + hasher.Write(node) + } - // Validate the Merkle proof - isValid, err := utilsProof.ValidateMerkleProof(root, leafObject, proof) - if err != nil { - fmt.Printf("Error in validating Merkle proof: %v", err) - return false + // Update the derived root + derivedRootNew := sha256.Sum256(hasher.Sum(nil)) + derivedRoot = derivedRootNew[:] } - // Verify against the attested header's state root - return isValid && bytes.Equal(root, attestedHeader.StateRoot[:]) + // Compare the final derived root with the attested header's state root + return bytes.Equal(derivedRoot[:], attestedHeader.StateRoot[:]) } + func CalculateForkVersion(forks *config.Forks, slot uint64) [4]byte { epoch := slot / 32 diff --git a/utils/proof/merkleProof.go b/utils/proof/merkleProof.go index b28bc54..3610c11 100644 --- a/utils/proof/merkleProof.go +++ b/utils/proof/merkleProof.go @@ -6,7 +6,7 @@ import ( merkletree "github.com/wealdtech/go-merkletree" ) -func ValidateMerkleProof( +func validateMerkleProof( root []byte, leaf []byte, proof *merkletree.Proof, diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index e68f30d..0000000 --- a/utils/utils.go +++ /dev/null @@ -1,64 +0,0 @@ -package utils - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "strings" - - "github.com/BlocSoc-iitr/selene/common" -) - -// if we need to export the functions , just make their first letter capitalised -func Hex_str_to_bytes(s string) ([]byte, error) { - s = strings.TrimPrefix(s, "0x") - - bytesArray, err := hex.DecodeString(s) - - if err != nil { - return nil, err - } - - return bytesArray, nil -} - -func Address_to_hex_string(addr common.Address) string { - bytesArray := addr.Bytes() - return fmt.Sprintf("0x%x", hex.EncodeToString(bytesArray)) -} - -func U64_to_hex_string(val uint64) string { - return fmt.Sprintf("0x%x", val) -} - -func BytesSerialise(bytes []byte) ([]byte, error) { - - if bytes == nil { - return json.Marshal(nil) - } - bytesString := hex.EncodeToString(bytes) - result, err := json.Marshal(bytesString) - if err != nil { - return nil, err - } - return result, nil - -} - -func BytesDeserialise(data []byte) ([]byte, error) { - var bytesOpt *string - if err := json.Unmarshal(data, &bytesOpt); err != nil { - return nil, err - } - - if bytesOpt == nil { - return nil, nil - } else { - bytes, err := common.Hex_str_to_bytes(*bytesOpt) - if err != nil { - return nil, err - } - return bytes, nil - } - -} From 1e0fb72e436e30e384ebf41f012d59b5d15d1ace Mon Sep 17 00:00:00 2001 From: DarkLord017 Date: Sat, 21 Sep 2024 18:12:21 +0530 Subject: [PATCH 6/9] Used merkle tree library for utils --- consensus/utils.go | 74 +++++++++++++++++--------------------- utils/proof/merkleProof.go | 2 +- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/consensus/utils.go b/consensus/utils.go index 6399968..1c0727f 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -6,16 +6,18 @@ import ( "encoding/json" "fmt" "log" - "math" "github.com/BlocSoc-iitr/selene/config" "github.com/BlocSoc-iitr/selene/consensus/consensus_core" "github.com/BlocSoc-iitr/selene/utils/bls" + utilsProof "github.com/BlocSoc-iitr/selene/utils/proof" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" - "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" + merkletree "github.com/wealdtech/go-merkletree" ) // TreeHashRoot computes the Merkle root from the provided leaves in a flat []byte slice. +// TreeHashRoot calculates the root hash from the input data. func TreeHashRoot(data []byte) ([]byte, error) { // Convert the input data into a slice of leaves leaves, err := bytesToLeaves(data) @@ -23,28 +25,19 @@ func TreeHashRoot(data []byte) ([]byte, error) { return nil, err } - nodes := leaves // Start with the leaf nodes - - for len(nodes) > 1 { - var newLevel [][]byte - - // Pair nodes and hash them - for i := 0; i < len(nodes); i += 2 { - if i+1 < len(nodes) { - // Hash pair of nodes - nodeHash := crypto.Keccak256(append(nodes[i], nodes[i+1]...)) - newLevel = append(newLevel, nodeHash) - } else { - // Handle odd number of nodes (carry last node up) - newLevel = append(newLevel, nodes[i]) - } - } + // Create the Merkle tree using the leaves + tree, err := merkletree.New(leaves) + if err != nil { + return nil, err + } - nodes = newLevel + // Fetch the root hash of the tree + root := tree.Root() + if root == nil { + return nil, errors.New("failed to calculate the Merkle root") } - // Return the root hash - return nodes[0], nil + return root, nil } func bytesToLeaves(data []byte) ([][]byte, error) { @@ -100,36 +93,33 @@ func isProofValid( return false } - // Compute the root hash of the leaf object - derivedRoot, err := TreeHashRoot(leafObject) + // Create the tree from the branch data + tree, err := merkletree.New(branch) if err != nil { + fmt.Printf("Error creating Merkle tree: %v", err) return false } - // Iterate through the branch and compute the Merkle root - for i, node := range branch { - hasher := sha256.New() + // Fetch the root hash of the tree + root := tree.Root() - // Check if index / 2^i is odd or even - if (index/int(math.Pow(2, float64(i))))%2 != 0 { - // If odd, hash(node || derived_root) - hasher.Write(node) - hasher.Write(derivedRoot[:]) - } else { - // If even, hash(derived_root || node) - hasher.Write(derivedRoot[:]) - hasher.Write(node) - } + // Validate the Merkle proof by generating it for the leaf object + proof, err := tree.GenerateProof(leafObject) + if err != nil { + fmt.Printf("Error generating proof: %v", err) + return false + } - // Update the derived root - derivedRootNew := sha256.Sum256(hasher.Sum(nil)) - derivedRoot = derivedRootNew[:] + // Validate the Merkle proof + isValid, err := utilsProof.ValidateMerkleProof(root, leafObject, proof) + if err != nil { + fmt.Printf("Error in validating Merkle proof: %v", err) + return false } - // Compare the final derived root with the attested header's state root - return bytes.Equal(derivedRoot[:], attestedHeader.StateRoot[:]) + // Verify against the attested header's state root + return isValid && bytes.Equal(root, attestedHeader.StateRoot[:]) } - func CalculateForkVersion(forks *config.Forks, slot uint64) [4]byte { epoch := slot / 32 diff --git a/utils/proof/merkleProof.go b/utils/proof/merkleProof.go index 3610c11..b28bc54 100644 --- a/utils/proof/merkleProof.go +++ b/utils/proof/merkleProof.go @@ -6,7 +6,7 @@ import ( merkletree "github.com/wealdtech/go-merkletree" ) -func validateMerkleProof( +func ValidateMerkleProof( root []byte, leaf []byte, proof *merkletree.Proof, From 3ad29c6d5c54bd86f2c270e2055ed0f3ac794bfa Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:35:20 +0530 Subject: [PATCH 7/9] Update utils.go Using index too for merkle proof validation --- consensus/utils.go | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/consensus/utils.go b/consensus/utils.go index 1c0727f..f71952f 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -10,7 +10,8 @@ import ( "github.com/BlocSoc-iitr/selene/config" "github.com/BlocSoc-iitr/selene/consensus/consensus_core" "github.com/BlocSoc-iitr/selene/utils/bls" - utilsProof "github.com/BlocSoc-iitr/selene/utils/proof" + "github.com/ethereum/go-ethereum/crypto" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/pkg/errors" merkletree "github.com/wealdtech/go-merkletree" @@ -22,19 +23,19 @@ func TreeHashRoot(data []byte) ([]byte, error) { // Convert the input data into a slice of leaves leaves, err := bytesToLeaves(data) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting bytes to leaves: %w", err) } // Create the Merkle tree using the leaves - tree, err := merkletree.New(leaves) - if err != nil { - return nil, err + tree, errorCreatingMerkleTree := merkletree.New(leaves) + if errorCreatingMerkleTree != nil { + return nil, fmt.Errorf("error creating Merkle tree: %w", err) } // Fetch the root hash of the tree root := tree.Root() if root == nil { - return nil, errors.New("failed to calculate the Merkle root") + return nil, errors.New("failed to calculate the Merkle root: root is nil") } return root, nil @@ -93,7 +94,7 @@ func isProofValid( return false } - // Create the tree from the branch data + // Create the Merkle tree from the branch data tree, err := merkletree.New(branch) if err != nil { fmt.Printf("Error creating Merkle tree: %v", err) @@ -103,23 +104,38 @@ func isProofValid( // Fetch the root hash of the tree root := tree.Root() - // Validate the Merkle proof by generating it for the leaf object + // Validate the Merkle proof for the leaf object proof, err := tree.GenerateProof(leafObject) if err != nil { fmt.Printf("Error generating proof: %v", err) return false } - // Validate the Merkle proof - isValid, err := utilsProof.ValidateMerkleProof(root, leafObject, proof) - if err != nil { - fmt.Printf("Error in validating Merkle proof: %v", err) - return false + // Initialize the derived root as the leaf object's hash + derivedRoot := crypto.Keccak256(leafObject) + + // Iterate over the proof's hashes + for i, hash := range proof.Hashes { + hasher := sha256.New() + + // Use the index to determine how to combine the hashes + if (index>>i)&1 == 1 { + // If index is odd, hash node || derivedRoot + hasher.Write(hash) + hasher.Write(derivedRoot) + } else { + // If index is even, hash derivedRoot || node + hasher.Write(derivedRoot) + hasher.Write(hash) + } + // Update derivedRoot for the next level + derivedRoot = hasher.Sum(nil) } - // Verify against the attested header's state root - return isValid && bytes.Equal(root, attestedHeader.StateRoot[:]) + // Compare the final derived root with the attested header's state root + return bytes.Equal(derivedRoot, attestedHeader.StateRoot[:]) && bytes.Equal(root, attestedHeader.StateRoot[:]) } + func CalculateForkVersion(forks *config.Forks, slot uint64) [4]byte { epoch := slot / 32 From 0513fe7e782d4cc5362096e71193887bc8668400 Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Sat, 21 Sep 2024 19:12:28 +0530 Subject: [PATCH 8/9] Update utils.go using merkle tree library --- consensus/utils.go | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/consensus/utils.go b/consensus/utils.go index f71952f..303ca7a 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -10,7 +10,6 @@ import ( "github.com/BlocSoc-iitr/selene/config" "github.com/BlocSoc-iitr/selene/consensus/consensus_core" "github.com/BlocSoc-iitr/selene/utils/bls" - "github.com/ethereum/go-ethereum/crypto" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/pkg/errors" @@ -94,28 +93,16 @@ func isProofValid( return false } - // Create the Merkle tree from the branch data - tree, err := merkletree.New(branch) - if err != nil { - fmt.Printf("Error creating Merkle tree: %v", err) - return false - } - - // Fetch the root hash of the tree - root := tree.Root() + // Initialize the derived root as the leaf object's hash + derivedRoot, errFetchingTreeHashRoot := TreeHashRoot(leafObject) - // Validate the Merkle proof for the leaf object - proof, err := tree.GenerateProof(leafObject) - if err != nil { - fmt.Printf("Error generating proof: %v", err) + if errFetchingTreeHashRoot != nil { + fmt.Printf("Error fetching tree hash root: %v", errFetchingTreeHashRoot) return false } - // Initialize the derived root as the leaf object's hash - derivedRoot := crypto.Keccak256(leafObject) - // Iterate over the proof's hashes - for i, hash := range proof.Hashes { + for i, hash := range branch { hasher := sha256.New() // Use the index to determine how to combine the hashes @@ -133,7 +120,7 @@ func isProofValid( } // Compare the final derived root with the attested header's state root - return bytes.Equal(derivedRoot, attestedHeader.StateRoot[:]) && bytes.Equal(root, attestedHeader.StateRoot[:]) + return bytes.Equal(derivedRoot, attestedHeader.StateRoot[:]) } func CalculateForkVersion(forks *config.Forks, slot uint64) [4]byte { From 59b3babf94b4405fb42cf89913dd351859bc0498 Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Sat, 21 Sep 2024 21:14:24 +0530 Subject: [PATCH 9/9] Update consensus.go to resolve comments --- consensus/consensus.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index a4fc9b6..a685ffa 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -84,11 +84,6 @@ type LightClientStore struct { CurrentMaxActiveParticipants uint64 } -type Fork struct { - Version uint64 - Epoch uint64 -} - func (con ConsensusClient) New(rpc *string, config config.Config) ConsensusClient { blockSend := make(chan common.Block, 256) finalizedBlockSend := make(chan *common.Block) @@ -220,9 +215,6 @@ func sync_all_fallback(inner *Inner, chainID uint64) error { // Fetch the latest checkpoint from the network checkpoint := checkpointFallback.FetchLatestCheckpoint(networkName) - if err != nil { - return err - } // Sync using the inner struct's sync method if err := inner.sync(checkpoint); err != nil { @@ -246,7 +238,7 @@ func (in *Inner) New(rpcURL string, blockSend chan common.Block, finalizedBlockS } } -func (in *Inner) Get_rpc() error { +func (in *Inner) Check_rpc() error { chainID, err := in.RPC.ChainId() if err != nil { return err @@ -256,7 +248,7 @@ func (in *Inner) Get_rpc() error { } return nil } -func (in *Inner) check_execution_payload(ctx context.Context, slot *uint64) (*consensus_core.ExecutionPayload, error) { +func (in *Inner) get_execution_payload(ctx context.Context, slot *uint64) (*consensus_core.ExecutionPayload, error) { block, err := in.RPC.GetBlock(*slot) if err != nil { return nil, err @@ -447,14 +439,14 @@ func (in *Inner) sync(checkpoint [32]byte) error { func (in *Inner) send_blocks() error { // Get slot from the optimistic header slot := in.Store.OptimisticHeader.Slot - payload, err := in.check_execution_payload(context.Background(), &slot) + payload, err := in.get_execution_payload(context.Background(), &slot) if err != nil { return err } // Get finalized slot from the finalized header finalizedSlot := in.Store.FinalizedHeader.Slot - finalizedPayload, err := in.check_execution_payload(context.Background(), &finalizedSlot) + finalizedPayload, err := in.get_execution_payload(context.Background(), &finalizedSlot) if err != nil { return err } @@ -486,6 +478,8 @@ func (in *Inner) send_blocks() error { return nil } +// / Gets the duration until the next update +// / Updates are scheduled for 4 seconds into each slot func (in *Inner) duration_until_next_update() time.Duration { currentSlot := in.expected_current_slot() nextSlot := currentSlot + 1 @@ -498,6 +492,7 @@ func (in *Inner) duration_until_next_update() time.Duration { return time.Duration(nextUpdate) * time.Second } func (in *Inner) bootstrap(checkpoint [32]byte) { + bootstrap, errInBootstrap := in.RPC.GetBootstrap(checkpoint) if errInBootstrap != nil { log.Printf("failed to fetch bootstrap: %v", errInBootstrap)