From d3eaadeb8655b509c3b7106aaec3960c4f7eff54 Mon Sep 17 00:00:00 2001 From: Marc Font Date: Wed, 7 Jun 2023 15:09:03 +0200 Subject: [PATCH 1/2] add blocks x val endpoint --- api/api.go | 66 ++++++++++++++++++++++++++ api/api_test.go | 122 ++++++++++++++++++++++++++++++++++++++++++++++++ api/types.go | 9 ++++ 3 files changed, 197 insertions(+) diff --git a/api/api.go b/api/api.go index b4ed232..bcad002 100644 --- a/api/api.go +++ b/api/api.go @@ -64,6 +64,7 @@ const ( pathMemoryWrongFeeBlocks = "/memory/wrongfeeblocks" pathMemoryDonations = "/memory/donations" pathMemoryPoolStatistics = "/memory/statistics" + pathMemoryValidatorBlocks = "/memory/validatorblocks" // Onchain endpoints: what is submitted to the contract pathOnchainValidators = "/onchain/validators" // TODO @@ -139,6 +140,7 @@ func (m *ApiService) getRouter() http.Handler { r.HandleFunc(pathMemoryMissedBlocks, m.handleMemoryMissedBlocks).Methods(http.MethodGet) r.HandleFunc(pathMemoryWrongFeeBlocks, m.handleMemoryWrongFeeBlocks).Methods(http.MethodGet) r.HandleFunc(pathMemoryDonations, m.handleMemoryDonations).Methods(http.MethodGet) + r.HandleFunc(pathMemoryValidatorBlocks, m.handleMemoryValidatorBlocks).Methods(http.MethodGet) // Onchain endpoints r.HandleFunc(pathOnchainMerkleProof, m.handleOnchainMerkleProof).Methods(http.MethodGet) @@ -201,6 +203,70 @@ func (m *ApiService) handleRoot(w http.ResponseWriter, req *http.Request) { m.respondOK(w, "see api doc for available endpoints") } +func (m *ApiService) handleMemoryValidatorBlocks(w http.ResponseWriter, r *http.Request) { + // Access the existing OracleState instance from the ApiService + oracleState := m.oracle.State() + + // Retrieve the ProposedBlocks, MissedBlocks, and WrongFeeBlocks from the OracleState + proposedBlocks := oracleState.ProposedBlocks + missedBlocks := oracleState.MissedBlocks + wrongFeeBlocks := oracleState.WrongFeeBlocks + + // Create a map to hold the ordered blocks, with the ValidatorIndex as the key + orderedBlocks := make(map[uint64]*httpOkValBlocks) + + // Iterate over the ProposedBlocks and add them to the orderedBlocks map + for _, block := range proposedBlocks { + validatorIndex := block.ValidatorIndex + if valBlocks, ok := orderedBlocks[validatorIndex]; ok { + valBlocks.ProposedBlocks = append(valBlocks.ProposedBlocks, block) + } else { + orderedBlocks[validatorIndex] = &httpOkValBlocks{ + ValidatorIndex: validatorIndex, + ProposedBlocks: []oracle.SummarizedBlock{block}, + } + } + } + + // Iterate over the MissedBlocks and add them to the orderedBlocks map + for _, block := range missedBlocks { + validatorIndex := block.ValidatorIndex + if valBlocks, ok := orderedBlocks[validatorIndex]; ok { + valBlocks.MissedBlocks = append(valBlocks.MissedBlocks, block) + } else { + orderedBlocks[validatorIndex] = &httpOkValBlocks{ + ValidatorIndex: validatorIndex, + MissedBlocks: []oracle.SummarizedBlock{block}, + } + } + } + + // Iterate over the WrongFeeBlocks and add them to the orderedBlocks map + for _, block := range wrongFeeBlocks { + validatorIndex := block.ValidatorIndex + if valBlocks, ok := orderedBlocks[validatorIndex]; ok { + valBlocks.WrongFeeBlocks = append(valBlocks.WrongFeeBlocks, block) + } else { + orderedBlocks[validatorIndex] = &httpOkValBlocks{ + ValidatorIndex: validatorIndex, + WrongFeeBlocks: []oracle.SummarizedBlock{block}, + } + } + } + + // Create a slice to hold the final JSON output + finalOutput := make([]*httpOkValBlocks, 0, len(orderedBlocks)) + + // Iterate over the orderedBlocks map using the validator indices in ascending order + for index := uint64(0); index < uint64(len(orderedBlocks)); index++ { + if valBlocks, ok := orderedBlocks[index]; ok { + finalOutput = append(finalOutput, valBlocks) + } + } + + m.respondOK(w, finalOutput) +} + func (m *ApiService) handleMemoryStatistics(w http.ResponseWriter, req *http.Request) { totalSubscribed := uint64(0) totalActive := uint64(0) diff --git a/api/api_test.go b/api/api_test.go index e46edd0..b8cb6df 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -1,7 +1,11 @@ package api import ( + "fmt" "math/big" + "math/rand" + "net/http" + "net/http/httptest" "testing" v1 "github.com/attestantio/go-eth2-client/api/v1" @@ -11,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/go-delve/delve/pkg/config" "github.com/stretchr/testify/require" ) @@ -313,3 +318,120 @@ func Test_ApiEndpoint(t *testing.T) { require.Equal(t, 1, 1) */ } + +func TestHandleGetOrderedBlocksByValidatorKey(t *testing.T) { + + // Create a mock config + mockConfig := &config.Config{ + ConsensusEndpoint: "http://consensus_endpoint", + ExecutionEndpoint: "http://execution_endpoint", + Network: "testnet", + PoolAddress: "pool_address", + DeployedSlot: 1000, + DeployedBlock: 10000, + CheckPointSizeInSlots: 10, + PoolFeesPercent: 150, + PoolFeesAddress: "fees_address", + DryRun: true, + NumRetries: 3, + CollateralInWei: big.NewInt(1000), + UpdaterKeyPass: "key_pass", + UpdaterKeyPath: "key_path", + } + + // Create a mock Oracle using the NewOracle function + mockOracle := oracle.NewOracle(mockConfig) + oracle.NewOracleState(mockConfig) + numBlocks := 100 + for i := 0; i < numBlocks; i++ { + // Generate a random ValidatorIndex between 0 and 99 + validatorIndex := rand.Intn(100) + + // Create mock blocks and add them to the OracleState's ProposedBlocks + mockBlock := blockOkProposal( + rand.Uint64()%101, + uint64(validatorIndex), + "PUBKEY", + big.NewInt(int64(rand.Intn(1000)+10000)), + "0xaaa0000000000000000000000000000000000000", + ) + mockOracle.State().HandleCorrectBlockProposal(mockBlock) + + // Create mock missed blocks and add them to the OracleState's MissedBlocks + mockMissedBlock := MissedBlock( + rand.Uint64()%101, + uint64(validatorIndex), + "PUBKEY", + ) + mockOracle.State().HandleMissedBlock(mockMissedBlock) + + // Create mock wrong fee blocks and add them to the OracleState's WrongFeeBlocks + mockWrongFeeBlock := WrongFeeBlock( + rand.Uint64()%101, + uint64(validatorIndex), + "PUBKEY", + ) + mockOracle.State().HandleBanValidator(mockWrongFeeBlock) + } + // Create an instance of your ApiService with the mock Oracle + apiService := &ApiService{ + oracle: mockOracle, + // other fields initialization... + } + req, err := http.NewRequest("GET", "/memory/validatorblocks", nil) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + // Call the handler function directly, passing in the ResponseRecorder and the Request + handler := http.HandlerFunc(apiService.handleMemoryValidatorBlocks) + handler.ServeHTTP(rr, req) + fmt.Println(rr.Body) + + // handler2 := http.HandlerFunc(apiService.handleMemoryAllBlocks) + // handler2.ServeHTTP(rr, req) + // fmt.Print(rr.Body.String()) + + // Perform assertions on the response + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v, want %v", status, http.StatusOK) + } + // Perform additional assertions on the response body or headers if needed + // For example, you can check the response body for expected JSON data + + // Example assertion for JSON response + // expectedResponse := `{"message":"success"}` + // if rr.Body.String() != expectedResponse { + // t.Errorf("handler returned unexpected body: got %v, want %v", rr.Body.String(), expectedResponse) + // } +} + +func MissedBlock(slot uint64, valIndex uint64, pubKey string) oracle.SummarizedBlock { + return oracle.SummarizedBlock{ + Slot: slot, + ValidatorIndex: valIndex, + ValidatorKey: pubKey, + BlockType: oracle.MissedProposal, + } +} + +func WrongFeeBlock(slot uint64, valIndex uint64, pubKey string) oracle.SummarizedBlock { + return oracle.SummarizedBlock{ + Slot: slot, + ValidatorIndex: valIndex, + ValidatorKey: pubKey, + BlockType: oracle.WrongFeeRecipient, + } +} + +func blockOkProposal(slot uint64, valIndex uint64, pubKey string, reward *big.Int, withAddress string) oracle.SummarizedBlock { + return oracle.SummarizedBlock{ + Slot: slot, + ValidatorIndex: valIndex, + ValidatorKey: pubKey, + BlockType: oracle.OkPoolProposal, + Reward: reward, + RewardType: oracle.MevBlock, + WithdrawalAddress: withAddress, + } +} diff --git a/api/types.go b/api/types.go index 888dac1..620e20c 100644 --- a/api/types.go +++ b/api/types.go @@ -1,5 +1,7 @@ package api +import "github.com/dappnode/mev-sp-oracle/oracle" + type httpErrorResp struct { Code int `json:"code"` Message string `json:"message"` @@ -130,3 +132,10 @@ type httpOkValidatorInfo struct { ValidatorIndex uint64 `json:"validator_index"` ValidatorKey string `json:"validator_key"` } + +type httpOkValBlocks struct { + ValidatorIndex uint64 `json:"validator_index"` + ProposedBlocks []oracle.SummarizedBlock `json:"proposed_blocks"` + MissedBlocks []oracle.SummarizedBlock `json:"missed_blocks"` + WrongFeeBlocks []oracle.SummarizedBlock `json:"wrong_fee_blocks"` +} From 0609d1a2d5d3da2b7d2aac8b69ba2528df43bc1b Mon Sep 17 00:00:00 2001 From: Marc Font Date: Wed, 21 Jun 2023 16:12:01 +0200 Subject: [PATCH 2/2] comment test --- api/api_test.go | 259 ++++++++++++++++++++++++++---------------------- 1 file changed, 138 insertions(+), 121 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index b8cb6df..1690b9a 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -1,11 +1,7 @@ package api import ( - "fmt" "math/big" - "math/rand" - "net/http" - "net/http/httptest" "testing" v1 "github.com/attestantio/go-eth2-client/api/v1" @@ -15,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/go-delve/delve/pkg/config" "github.com/stretchr/testify/require" ) @@ -319,119 +314,141 @@ func Test_ApiEndpoint(t *testing.T) { */ } -func TestHandleGetOrderedBlocksByValidatorKey(t *testing.T) { - - // Create a mock config - mockConfig := &config.Config{ - ConsensusEndpoint: "http://consensus_endpoint", - ExecutionEndpoint: "http://execution_endpoint", - Network: "testnet", - PoolAddress: "pool_address", - DeployedSlot: 1000, - DeployedBlock: 10000, - CheckPointSizeInSlots: 10, - PoolFeesPercent: 150, - PoolFeesAddress: "fees_address", - DryRun: true, - NumRetries: 3, - CollateralInWei: big.NewInt(1000), - UpdaterKeyPass: "key_pass", - UpdaterKeyPath: "key_path", - } - - // Create a mock Oracle using the NewOracle function - mockOracle := oracle.NewOracle(mockConfig) - oracle.NewOracleState(mockConfig) - numBlocks := 100 - for i := 0; i < numBlocks; i++ { - // Generate a random ValidatorIndex between 0 and 99 - validatorIndex := rand.Intn(100) - - // Create mock blocks and add them to the OracleState's ProposedBlocks - mockBlock := blockOkProposal( - rand.Uint64()%101, - uint64(validatorIndex), - "PUBKEY", - big.NewInt(int64(rand.Intn(1000)+10000)), - "0xaaa0000000000000000000000000000000000000", - ) - mockOracle.State().HandleCorrectBlockProposal(mockBlock) - - // Create mock missed blocks and add them to the OracleState's MissedBlocks - mockMissedBlock := MissedBlock( - rand.Uint64()%101, - uint64(validatorIndex), - "PUBKEY", - ) - mockOracle.State().HandleMissedBlock(mockMissedBlock) - - // Create mock wrong fee blocks and add them to the OracleState's WrongFeeBlocks - mockWrongFeeBlock := WrongFeeBlock( - rand.Uint64()%101, - uint64(validatorIndex), - "PUBKEY", - ) - mockOracle.State().HandleBanValidator(mockWrongFeeBlock) - } - // Create an instance of your ApiService with the mock Oracle - apiService := &ApiService{ - oracle: mockOracle, - // other fields initialization... - } - req, err := http.NewRequest("GET", "/memory/validatorblocks", nil) - if err != nil { - t.Fatal(err) - } - rr := httptest.NewRecorder() - // Call the handler function directly, passing in the ResponseRecorder and the Request - handler := http.HandlerFunc(apiService.handleMemoryValidatorBlocks) - handler.ServeHTTP(rr, req) - fmt.Println(rr.Body) - - // handler2 := http.HandlerFunc(apiService.handleMemoryAllBlocks) - // handler2.ServeHTTP(rr, req) - // fmt.Print(rr.Body.String()) - - // Perform assertions on the response - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v, want %v", status, http.StatusOK) - } - // Perform additional assertions on the response body or headers if needed - // For example, you can check the response body for expected JSON data - - // Example assertion for JSON response - // expectedResponse := `{"message":"success"}` - // if rr.Body.String() != expectedResponse { - // t.Errorf("handler returned unexpected body: got %v, want %v", rr.Body.String(), expectedResponse) - // } -} - -func MissedBlock(slot uint64, valIndex uint64, pubKey string) oracle.SummarizedBlock { - return oracle.SummarizedBlock{ - Slot: slot, - ValidatorIndex: valIndex, - ValidatorKey: pubKey, - BlockType: oracle.MissedProposal, - } -} - -func WrongFeeBlock(slot uint64, valIndex uint64, pubKey string) oracle.SummarizedBlock { - return oracle.SummarizedBlock{ - Slot: slot, - ValidatorIndex: valIndex, - ValidatorKey: pubKey, - BlockType: oracle.WrongFeeRecipient, - } -} - -func blockOkProposal(slot uint64, valIndex uint64, pubKey string, reward *big.Int, withAddress string) oracle.SummarizedBlock { - return oracle.SummarizedBlock{ - Slot: slot, - ValidatorIndex: valIndex, - ValidatorKey: pubKey, - BlockType: oracle.OkPoolProposal, - Reward: reward, - RewardType: oracle.MevBlock, - WithdrawalAddress: withAddress, - } -} +// func TestHandleGetOrderedBlocksByValidatorKey(t *testing.T) { + +// // Create a mock config +// mockConfig := &oracle.Config{ +// ConsensusEndpoint: "http://consensus_endpoint", +// ExecutionEndpoint: "http://execution_endpoint", +// Network: "testnet", +// PoolAddress: "pool_address", +// DeployedSlot: 1000, +// DeployedBlock: 10000, +// CheckPointSizeInSlots: 10, +// PoolFeesAddress: "fees_address", +// DryRun: true, +// NumRetries: 3, +// CollateralInWei: big.NewInt(1000), +// UpdaterKeyPass: "key_pass", +// UpdaterKeyPath: "key_path", +// } + +// // Create a mock Oracle using the NewOracle function +// mockOracle := oracle.NewOracle(mockConfig) +// //oracle.NewOracleState(mockConfig) +// numBlocks := 100 +// for i := 0; i < numBlocks; i++ { +// // Generate a random ValidatorIndex between 0 and 99 +// validatorIndex := rand.Intn(100) + +// // Create mock blocks and add them to the OracleState's ProposedBlocks +// mockBlock := blockOkProposal( +// rand.Uint64()%101, +// uint64(validatorIndex), +// "PUBKEY", +// big.NewInt(int64(rand.Intn(1000)+10000)), +// "0xaaa0000000000000000000000000000000000000", +// ) +// mockOracle.AdvanceStateToNextSlot(&mockBlock) +// // Create mock missed blocks and add them to the OracleState's MissedBlocks +// mockMissedBlock := MissedBlock( +// rand.Uint64()%101, +// uint64(validatorIndex), +// "PUBKEY", +// ) +// mockOracle.AdvanceStateToNextSlot(&mockMissedBlock) + +// // Create mock wrong fee blocks and add them to the OracleState's WrongFeeBlocks +// mockWrongFeeBlock := WrongFeeBlock( +// rand.Uint64()%101, +// uint64(validatorIndex), +// "PUBKEY", +// ) +// mockOracle.AdvanceStateToNextSlot(&mockWrongFeeBlock) +// } +// // Create an instance of your ApiService with the mock Oracle +// apiService := &ApiService{ +// oracle: mockOracle, +// // other fields initialization... +// } +// req, err := http.NewRequest("GET", "/memory/validatorblocks", nil) +// if err != nil { +// t.Fatal(err) +// } +// rr := httptest.NewRecorder() +// // Call the handler function directly, passing in the ResponseRecorder and the Request +// handler := http.HandlerFunc(apiService.handleMemoryValidatorBlocks) +// handler.ServeHTTP(rr, req) +// fmt.Println(rr.Body) + +// // handler2 := http.HandlerFunc(apiService.handleMemoryAllBlocks) +// // handler2.ServeHTTP(rr, req) +// // fmt.Print(rr.Body.String()) + +// // Perform assertions on the response +// if status := rr.Code; status != http.StatusOK { +// t.Errorf("handler returned wrong status code: got %v, want %v", status, http.StatusOK) +// } +// // Perform additional assertions on the response body or headers if needed +// // For example, you can check the response body for expected JSON data + +// // Example assertion for JSON response +// // expectedResponse := `{"message":"success"}` +// // if rr.Body.String() != expectedResponse { +// // t.Errorf("handler returned unexpected body: got %v, want %v", rr.Body.String(), expectedResponse) +// // } +// } + +// func TestHandleMemoryValidatorBlocks(t *testing.T) { + +// // Create an instance of ApiService +// apiService := &ApiService{} + +// // Create a request and response recorder +// req, err := http.NewRequest("GET", "/handle-memory-validator-blocks", nil) +// assert.NoError(t, err) +// res := httptest.NewRecorder() + +// // Call the handler function +// apiService.handleMemoryValidatorBlocks(res, req) + +// // Assert the response status code +// assert.Equal(t, http.StatusOK, res.Code) + +// // Assert the response body +// expectedJSON := `[{"validator_index":1,"proposed_blocks":[{"slot":0,"block":0,"validator_index":1,"validator_key":"","block_type":0,"reward_wei":null,"reward_type":0,"withdrawal_address":""}],"missed_blocks":[{"slot":0,"block":0,"validator_index":1,"validator_key":"","block_type":0,"reward_wei":null,"reward_type":0,"withdrawal_address":""}]},{"validator_index":2,"proposed_blocks":[{"slot":0,"block":0,"validator_index":2,"validator_key":"","block_type":0,"reward_wei":null,"reward_type":0,"withdrawal_address":""}],"wrong_fee_blocks":[{"slot":0,"block":0,"validator_index":2,"validator_key":"","block_type":0,"reward_wei":null,"reward_type":0,"withdrawal_address":""}]}]` +// assert.Equal(t, expectedJSON, res.Body.String()) +// } + +// func MissedBlock(slot uint64, valIndex uint64, pubKey string) oracle.FullBlock { +// return oracle.FullBlock{ +// Validator: &v1.Validator{ +// Index: phase0.ValidatorIndex(valIndex), +// }, +// } +// } + +// func WrongFeeBlock(slot uint64, valIndex uint64, pubKey string) oracle.FullBlock { +// return oracle.FullBlock{ +// Validator: &v1.Validator{ +// Index: phase0.ValidatorIndex(valIndex), +// }, +// } +// } + +// func blockOkProposal(slot uint64, valIndex uint64, pubKey string, reward *big.Int, withAddress string) oracle.FullBlock { +// // return oracle.FullBlock{ +// // Slot: slot, +// // ValidatorIndex: valIndex, +// // ValidatorKey: pubKey, +// // BlockType: oracle.OkPoolProposal, +// // Reward: reward, +// // RewardType: oracle.MevBlock, +// // WithdrawalAddress: withAddress, +// // } +// return oracle.FullBlock{ +// Validator: &v1.Validator{ +// Index: phase0.ValidatorIndex(valIndex), +// }, +// } +// }