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..a685ffa 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -4,48 +4,1060 @@ 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 +} + +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 == [32]byte{} { + 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) + if err != nil { + return err + } + + ch := checkpoints.CheckpointFallback{} + + checkpointFallback, errWhileCheckpoint := ch.Build() + if errWhileCheckpoint != 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) + + // 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) Check_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) get_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, payload.ParentHash) + 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.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.get_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 +} + +// / 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 + 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 { + 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 + } + + // 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 != (consensus_core.Header{}) { + return ErrInvalidFinalityProof + } + + // 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 { + 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) + } + + 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 != (consensus_core.Header{}) { + 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 != (consensus_core.Header{}) && 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..303ca7a 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -1,15 +1,237 @@ 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" + + "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/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) + if err != nil { + return nil, fmt.Errorf("error converting bytes to leaves: %w", err) + } + + // Create the Merkle tree using the leaves + 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: root is nil") + } + + return root, 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 + } + + // Initialize the derived root as the leaf object's hash + derivedRoot, errFetchingTreeHashRoot := TreeHashRoot(leafObject) + + if errFetchingTreeHashRoot != nil { + fmt.Printf("Error fetching tree hash root: %v", errFetchingTreeHashRoot) + return false + } + + // Iterate over the proof's hashes + for i, hash := range branch { + 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) + } + + // 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= 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,