diff --git a/consensus/consensus.go b/consensus/consensus.go index 82a369b..863fc12 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -295,14 +295,16 @@ func (in *Inner) Check_rpc() error { func (in *Inner) get_execution_payload(slot *uint64) (*consensus_core.ExecutionPayload, error) { errorChan := make(chan error, 1) blockChan := make(chan consensus_core.BeaconBlock, 1) + versionChan := make(chan string , 1) go func() { var err error - block, err := in.RPC.GetBlock(*slot) + block,version, err := in.RPC.GetBlock(*slot) if err != nil { errorChan <- err } errorChan <- nil blockChan <- block + versionChan <- version }() if err := <-errorChan; err != nil { @@ -310,7 +312,8 @@ func (in *Inner) get_execution_payload(slot *uint64) (*consensus_core.ExecutionP } block := <-blockChan - Gethblock, err := beacon.BlockFromJSON("capella", block.Hash) + version := <-versionChan + Gethblock, err := beacon.BlockFromJSON(version, block.Hash) if err != nil { return nil, err } @@ -340,7 +343,7 @@ func (in *Inner) Get_payloads(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) + endBlock,_, err := in.RPC.GetBlock(endSlot) if err != nil { return nil, err } @@ -357,7 +360,7 @@ func (in *Inner) Get_payloads(startSlot, endSlot uint64) ([]interface{}, error) wg.Add(1) go func(slot uint64) { defer wg.Done() - block, err := in.RPC.GetBlock(slot) + block,_, err := in.RPC.GetBlock(slot) if err != nil { errorChan <- err return diff --git a/consensus/consensus_rpc_test.go b/consensus/consensus_rpc_test.go new file mode 100644 index 0000000..29505ea --- /dev/null +++ b/consensus/consensus_rpc_test.go @@ -0,0 +1,351 @@ +package consensus + +import ( + "encoding/hex" + "log" + "testing" + + "github.com/BlocSoc-iitr/selene/common" + "github.com/BlocSoc-iitr/selene/config" + "github.com/BlocSoc-iitr/selene/consensus/consensus_core" + "github.com/BlocSoc-iitr/selene/utils" +) + +func GetNewClient(strictCheckpointAge bool, sync bool) (*Inner, error) { + var n config.Network + baseConfig, err := n.BaseConfig("MAINNET") + if err != nil { + return nil, err + } + + checkpoint := "b21924031f38635d45297d68e7b7a408d40b194d435b25eeccad41c522841bd5" + consensusRpcUrl := "http://testing.mainnet.beacon-api.nimbus.team" + executionRpcUrl := "https://eth-mainnet.g.alchemy.com/v2/KLk2JrSPcjR8dp55N7XNTs9jeKTKHMoA" + + config := &config.Config{ + ConsensusRpc: consensusRpcUrl, + ExecutionRpc: executionRpcUrl, + Chain: baseConfig.Chain, + Forks: baseConfig.Forks, + StrictCheckpointAge: strictCheckpointAge, + } + //Decode the hex string into a byte slice + checkpointBytes, err := hex.DecodeString(checkpoint) + checkpointBytes32 := [32]byte{} + copy(checkpointBytes32[:], checkpointBytes) + if err != nil { + log.Fatalf("failed to decode checkpoint: %v", err) + } + + blockSend := make(chan *common.Block, 256) + finalizedBlockSend := make(chan *common.Block) + channelSend := make(chan *[]byte) + + In := Inner{} + client := In.New( + consensusRpcUrl, + blockSend, + finalizedBlockSend, + channelSend, + config, + ) + + if sync { + err := client.sync(checkpointBytes32) + if err != nil { + return nil, err + } + } else { + client.bootstrap(checkpointBytes32) + } + + return client, nil +} + +// testVerifyUpdate runs the test and returns its result via a channel (no inputs required). +func TestRpcVerifyUpdate(t *testing.T) { + //Get the client + client, err := GetNewClient(false, false) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + //Calculate the sync period + period := utils.CalcSyncPeriod(client.Store.FinalizedHeader.Slot) + + //Fetch updates + updates, err := client.RPC.GetUpdates(period, MAX_REQUEST_LIGHT_CLIENT_UPDATES) + if err != nil { + t.Fatalf("failed to get updates: %v", err) + } + + //Ensure we have updates to verify + if len(updates) == 0 { + t.Fatalf("no updates fetched") + } + + //Verify the first update + update := updates[0] + err = client.verify_update(&update) + if err != nil { + t.Fatalf("failed to verify update: %v", err) + } + +} + +func TestRpcVerifyUpdateInvalidCommittee(t *testing.T) { + client, err := GetNewClient(false, false) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + period := utils.CalcSyncPeriod(client.Store.FinalizedHeader.Slot) + updates, err := client.RPC.GetUpdates(period, MAX_REQUEST_LIGHT_CLIENT_UPDATES) + if err != nil { + t.Fatalf("failed to get updates: %v", err) + } + + if len(updates) == 0 { + t.Fatalf("no updates fetched") + } + + update := updates[0] + update.NextSyncCommittee.Pubkeys[0] = consensus_core.BLSPubKey{} // Invalid public key + + err = client.verify_update(&update) + if err == nil || err.Error() != "invalid next sync committee proof" { + t.Fatalf("expected 'invalid next sync committee proof', got %v", err) + } +} + +func TestRpcVerifyUpdateInvalidFinality(t *testing.T) { + client, err := GetNewClient(false, false) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + period := utils.CalcSyncPeriod(client.Store.FinalizedHeader.Slot) + updates, err := client.RPC.GetUpdates(period, MAX_REQUEST_LIGHT_CLIENT_UPDATES) + if err != nil { + t.Fatalf("failed to get updates: %v", err) + } + + if len(updates) == 0 { + t.Fatalf("no updates fetched") + } + + update := updates[0] + update.FinalizedHeader = consensus_core.Header{} // Assuming an empty header is invalid + + err = client.verify_update(&update) + if err == nil || err.Error() != "invalid finality proof" { + t.Fatalf("expected 'invalid finality proof', got %v", err) + } +} + +func TestRpcVerifyUpdateInvalidSig(t *testing.T) { + client, err := GetNewClient(false, false) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + period := utils.CalcSyncPeriod(client.Store.FinalizedHeader.Slot) + updates, err := client.RPC.GetUpdates(period, MAX_REQUEST_LIGHT_CLIENT_UPDATES) + if err != nil { + t.Fatalf("failed to get updates: %v", err) + } + + if len(updates) == 0 { + t.Fatalf("no updates fetched") + } + + update := updates[0] + update.SyncAggregate.SyncCommitteeSignature = consensus_core.SignatureBytes{} // Assuming an empty signature is invalid + + err = client.verify_update(&update) + if err == nil || err.Error() != "invalid signature" { + t.Fatalf("expected 'invalid signature', got %v", err) + } +} + +func TestRpcVerifyFinality(t *testing.T) { + //Get the client + client, err := GetNewClient(false, true) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + //Fetch the finality update + update, err := client.RPC.GetFinalityUpdate() + if err != nil { + t.Fatalf("failed to get finality update: %v", err) + } + + //Verify the finality update + err = client.verify_finality_update(&update) + if err != nil { + t.Fatalf("finality verification failed: %v", err) + } +} + +func TestRpcVerifyFinalityInvalidFinality(t *testing.T) { + //Get the client + client, err := GetNewClient(false, true) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + //Fetch the finality update + update, err := client.RPC.GetFinalityUpdate() + if err != nil { + t.Fatalf("failed to get finality update: %v", err) + } + + //Modify the finalized header to be invalid + update.FinalizedHeader = consensus_core.Header{} //Assuming an empty header is invalid + + //Verify the finality update and expect an error + err = client.verify_finality_update(&update) + if err == nil { + t.Fatalf("expected error, got nil") + } + + //Check if the error matches the expected error message + expectedErr := "invalid finality proof" + if err.Error() != expectedErr { + t.Errorf("expected %s, got %v", expectedErr, err) + } +} + +func TestRpcVerifyFinalityInvalidSignature(t *testing.T) { + //Get the client + client, err := GetNewClient(false, true) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + //Fetch the finality update + update, err := client.RPC.GetFinalityUpdate() + if err != nil { + t.Fatalf("failed to get finality update: %v", err) + } + + //Modify the sync aggregate signature to be invalid + update.SyncAggregate.SyncCommitteeSignature = consensus_core.SignatureBytes{} //Assuming an empty signature is invalid + + //Verify the finality update and expect an error + err = client.verify_finality_update(&update) + if err == nil { + t.Fatalf("expected error, got nil") + } + + //Check if the error matches the expected error message + expectedErr := "invalid signature" + if err.Error() != expectedErr { + t.Errorf("expected %s, got %v", expectedErr, err) + } +} + +func TestRpcVerifyOptimistic(t *testing.T) { + //Get the client + client, err := GetNewClient(false, true) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + //Fetch the optimistic update + update, err := client.RPC.GetOptimisticUpdate() + if err != nil { + t.Fatalf("failed to get optimistic update: %v", err) + } + + //Verify the optimistic update + err = client.verify_optimistic_update(&update) + if err != nil { + t.Fatalf("optimistic verification failed: %v", err) + } +} +func TestRpcVerifyOptimisticInvalidSignature(t *testing.T) { + //Get the client + client, err := GetNewClient(false, true) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + //Fetch the optimistic update + update, err := client.RPC.GetOptimisticUpdate() + if err != nil { + t.Fatalf("failed to get optimistic update: %v", err) + } + + //Modify the sync aggregate signature to be invalid + update.SyncAggregate.SyncCommitteeSignature = consensus_core.SignatureBytes{} //Assuming an empty signature is invalid + + //Verify the optimistic update and expect an error + err = client.verify_optimistic_update(&update) + if err == nil { + t.Fatalf("expected error, got nil") + } + + //Check if the error matches the expected error message + expectedErr := "invalid signature" + if err.Error() != expectedErr { + t.Errorf("expected %s, got %v", expectedErr, err) + } +} +func TestRpcVerifyCheckpointAgeInvalid(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic due to invalid checkpoint age, but no panic occurred") + } else { + expectedPanicMessage := "checkpoint too old, consider using a more recent checkpoint" + if msg, ok := r.(string); ok && msg != expectedPanicMessage { + t.Errorf("expected panic message '%s', got '%s'", expectedPanicMessage, msg) + } + } + }() + + // This should trigger a panic due to the invalid checkpoint age + _, err := GetNewClient(true, false) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } +} + +func TestRpcSendBlocks(t *testing.T) { + //Get the client + client, err := GetNewClient(false, true) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + errAdvancongClient := client.advance() + if errAdvancongClient != nil { + t.Fatalf("failed to advance client: %v", errAdvancongClient) + } + //Send the blocks + errSendingBlock := client.send_blocks() + if errSendingBlock != nil { + t.Fatalf("failed to send blocks: %v", errSendingBlock) + } + +} + +func TestRpcGetPayloads(t *testing.T) { + //Get the client + client, err := GetNewClient(false, true) + if err != nil { + t.Fatalf("failed to get client: %v", err) + } + + //Fetch the payloads + payloads, err := client.Get_payloads(7109344, 7109344) + if err != nil { + t.Fatalf("failed to get payloads: %v", err) + } + + //Ensure we have payloads + if len(payloads) == 0 { + t.Fatalf("no payloads fetched") + } +} diff --git a/consensus/rpc/consensus_rpc.go b/consensus/rpc/consensus_rpc.go index f31ac85..5968a48 100644 --- a/consensus/rpc/consensus_rpc.go +++ b/consensus/rpc/consensus_rpc.go @@ -11,7 +11,7 @@ type ConsensusRpc interface { GetUpdates(period uint64, count uint8) ([]consensus_core.Update, error) GetFinalityUpdate() (consensus_core.FinalityUpdate, error) GetOptimisticUpdate() (consensus_core.OptimisticUpdate, error) - GetBlock(slot uint64) (consensus_core.BeaconBlock, error) + GetBlock(slot uint64) (consensus_core.BeaconBlock,string, error) ChainId() (uint64, error) } diff --git a/consensus/rpc/mock_rpc.go b/consensus/rpc/mock_rpc.go index 2fbee22..f2562c5 100644 --- a/consensus/rpc/mock_rpc.go +++ b/consensus/rpc/mock_rpc.go @@ -81,18 +81,18 @@ func (m *MockRpc) GetOptimisticUpdate() (consensus_core.OptimisticUpdate, error) } return optimistic.Data, nil } -func (m *MockRpc) GetBlock(slot uint64) (consensus_core.BeaconBlock, error) { +func (m *MockRpc) GetBlock(slot uint64) (consensus_core.BeaconBlock,string, error) { path := filepath.Join(m.testdata, fmt.Sprintf("blocks/%d.json", slot)) res, err := os.ReadFile(path) if err != nil { - return consensus_core.BeaconBlock{}, fmt.Errorf("failed to read file: %w", err) + return consensus_core.BeaconBlock{},"", fmt.Errorf("failed to read file: %w", err) } var block BeaconBlockResponse err = json.Unmarshal(res, &block) if err != nil { - return consensus_core.BeaconBlock{}, err + return consensus_core.BeaconBlock{},"", err } - return block.Data.Message, nil + return block.Data.Message,block.Version, nil } func (m *MockRpc) ChainId() (uint64, error) { return 0, fmt.Errorf("not implemented") diff --git a/consensus/rpc/mock_rpc_test.go b/consensus/rpc/mock_rpc_test.go index 8215bb9..4fff58a 100644 --- a/consensus/rpc/mock_rpc_test.go +++ b/consensus/rpc/mock_rpc_test.go @@ -205,7 +205,7 @@ func TestGetBlock(t *testing.T) { } mockRpc := NewMockRpc(tempDir) - block, err := mockRpc.GetBlock(4000) + block,_, err := mockRpc.GetBlock(4000) if err != nil { t.Fatalf("GetBlock failed: %v", err) } diff --git a/consensus/rpc/nimbus_rpc.go b/consensus/rpc/nimbus_rpc.go index 49b66fa..7b59852 100644 --- a/consensus/rpc/nimbus_rpc.go +++ b/consensus/rpc/nimbus_rpc.go @@ -96,14 +96,14 @@ func (n *NimbusRpc) GetOptimisticUpdate() (consensus_core.OptimisticUpdate, erro } return res.Data, nil } -func (n *NimbusRpc) GetBlock(slot uint64) (consensus_core.BeaconBlock, error) { +func (n *NimbusRpc) GetBlock(slot uint64) (consensus_core.BeaconBlock,string, error) { req := fmt.Sprintf("%s/eth/v2/beacon/blocks/%s", n.rpc, strconv.FormatUint(slot, 10)) var res BeaconBlockResponse err := get(req, &res) if err != nil { - return consensus_core.BeaconBlock{}, fmt.Errorf("block error: %w", err) + return consensus_core.BeaconBlock{},"", fmt.Errorf("block error: %w", err) } - return res.Data.Message, nil + return res.Data.Message,res.Version, nil } func (n *NimbusRpc) ChainId() (uint64, error) { req := fmt.Sprintf("%s/eth/v1/config/spec", n.rpc) @@ -118,6 +118,7 @@ func (n *NimbusRpc) ChainId() (uint64, error) { // 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 { + Version string Data BeaconBlockData } type BeaconBlockData struct { diff --git a/consensus/rpc/nimbus_rpc_test.go b/consensus/rpc/nimbus_rpc_test.go index 05cd0cd..d64e1fd 100644 --- a/consensus/rpc/nimbus_rpc_test.go +++ b/consensus/rpc/nimbus_rpc_test.go @@ -152,7 +152,7 @@ func TestNimbusGetBlock(t *testing.T) { })) defer server.Close() nimbusRpc := NewNimbusRpc(server.URL) - block, err := nimbusRpc.GetBlock(4000) + block,_, err := nimbusRpc.GetBlock(4000) assert.NoError(t, err) assert.Equal(t, uint64(4000), block.Slot) }