@@ -4947,3 +4947,193 @@ func TestGetPendingDeposits(t *testing.T) {
49474947 require .Equal (t , true , resp .Finalized )
49484948 })
49494949}
4950+
4951+ func TestGetPendingPartialWithdrawals (t * testing.T ) {
4952+ st , _ := util .DeterministicGenesisStateElectra (t , 10 )
4953+ for i := 0 ; i < 10 ; i += 1 {
4954+ err := st .AppendPendingPartialWithdrawal (
4955+ & eth.PendingPartialWithdrawal {
4956+ Index : primitives .ValidatorIndex (i ),
4957+ Amount : 100 ,
4958+ WithdrawableEpoch : primitives .Epoch (0 ),
4959+ })
4960+ require .NoError (t , err )
4961+ }
4962+ withdrawals , err := st .PendingPartialWithdrawals ()
4963+ require .NoError (t , err )
4964+
4965+ chainService := & chainMock.ChainService {
4966+ Optimistic : false ,
4967+ FinalizedRoots : map [[32 ]byte ]bool {},
4968+ }
4969+ server := & Server {
4970+ Stater : & testutil.MockStater {
4971+ BeaconState : st ,
4972+ },
4973+ OptimisticModeFetcher : chainService ,
4974+ FinalizationFetcher : chainService ,
4975+ }
4976+
4977+ t .Run ("json response" , func (t * testing.T ) {
4978+ req := httptest .NewRequest (http .MethodGet , "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals" , nil )
4979+ req .SetPathValue ("state_id" , "head" )
4980+ rec := httptest .NewRecorder ()
4981+ rec .Body = new (bytes.Buffer )
4982+
4983+ server .GetPendingPartialWithdrawals (rec , req )
4984+ require .Equal (t , http .StatusOK , rec .Code )
4985+ require .Equal (t , "electra" , rec .Header ().Get (api .VersionHeader ))
4986+
4987+ var resp structs.GetPendingPartialWithdrawalsResponse
4988+ require .NoError (t , json .Unmarshal (rec .Body .Bytes (), & resp ))
4989+
4990+ expectedVersion := version .String (st .Version ())
4991+ require .Equal (t , expectedVersion , resp .Version )
4992+
4993+ require .Equal (t , false , resp .ExecutionOptimistic )
4994+ require .Equal (t , false , resp .Finalized )
4995+
4996+ expectedWithdrawals := structs .PendingPartialWithdrawalsFromConsensus (withdrawals )
4997+ require .DeepEqual (t , expectedWithdrawals , resp .Data )
4998+ })
4999+
5000+ t .Run ("ssz response" , func (t * testing.T ) {
5001+ req := httptest .NewRequest (http .MethodGet , "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals" , nil )
5002+ req .Header .Set ("Accept" , "application/octet-stream" )
5003+ req .SetPathValue ("state_id" , "head" )
5004+ rec := httptest .NewRecorder ()
5005+ rec .Body = new (bytes.Buffer )
5006+
5007+ server .GetPendingPartialWithdrawals (rec , req )
5008+ require .Equal (t , http .StatusOK , rec .Code )
5009+ require .Equal (t , "electra" , rec .Header ().Get (api .VersionHeader ))
5010+
5011+ responseBytes := rec .Body .Bytes ()
5012+ var recoveredWithdrawals []* eth.PendingPartialWithdrawal
5013+
5014+ withdrawalSize := (& eth.PendingPartialWithdrawal {}).SizeSSZ ()
5015+ require .Equal (t , len (responseBytes ), withdrawalSize * len (withdrawals ))
5016+
5017+ for i := 0 ; i < len (withdrawals ); i ++ {
5018+ start := i * withdrawalSize
5019+ end := start + withdrawalSize
5020+
5021+ var withdrawal eth.PendingPartialWithdrawal
5022+ require .NoError (t , withdrawal .UnmarshalSSZ (responseBytes [start :end ]))
5023+ recoveredWithdrawals = append (recoveredWithdrawals , & withdrawal )
5024+ }
5025+ require .DeepEqual (t , withdrawals , recoveredWithdrawals )
5026+ })
5027+
5028+ t .Run ("pre electra state" , func (t * testing.T ) {
5029+ preElectraSt , _ := util .DeterministicGenesisStateDeneb (t , 1 )
5030+ preElectraServer := & Server {
5031+ Stater : & testutil.MockStater {
5032+ BeaconState : preElectraSt ,
5033+ },
5034+ OptimisticModeFetcher : chainService ,
5035+ FinalizationFetcher : chainService ,
5036+ }
5037+
5038+ // Test JSON request
5039+ req := httptest .NewRequest (http .MethodGet , "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals" , nil )
5040+ req .SetPathValue ("state_id" , "head" )
5041+ rec := httptest .NewRecorder ()
5042+ rec .Body = new (bytes.Buffer )
5043+
5044+ preElectraServer .GetPendingPartialWithdrawals (rec , req )
5045+ require .Equal (t , http .StatusBadRequest , rec .Code )
5046+
5047+ var errResp struct {
5048+ Code int `json:"code"`
5049+ Message string `json:"message"`
5050+ }
5051+ require .NoError (t , json .Unmarshal (rec .Body .Bytes (), & errResp ))
5052+ require .Equal (t , "state_id is prior to electra" , errResp .Message )
5053+
5054+ // Test SSZ request
5055+ sszReq := httptest .NewRequest (http .MethodGet , "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals" , nil )
5056+ sszReq .Header .Set ("Accept" , "application/octet-stream" )
5057+ sszReq .SetPathValue ("state_id" , "head" )
5058+ sszRec := httptest .NewRecorder ()
5059+ sszRec .Body = new (bytes.Buffer )
5060+
5061+ preElectraServer .GetPendingPartialWithdrawals (sszRec , sszReq )
5062+ require .Equal (t , http .StatusBadRequest , sszRec .Code )
5063+
5064+ var sszErrResp struct {
5065+ Code int `json:"code"`
5066+ Message string `json:"message"`
5067+ }
5068+ require .NoError (t , json .Unmarshal (sszRec .Body .Bytes (), & sszErrResp ))
5069+ require .Equal (t , "state_id is prior to electra" , sszErrResp .Message )
5070+ })
5071+
5072+ t .Run ("missing state_id parameter" , func (t * testing.T ) {
5073+ req := httptest .NewRequest (http .MethodGet , "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals" , nil )
5074+ // Intentionally not setting state_id
5075+ rec := httptest .NewRecorder ()
5076+ rec .Body = new (bytes.Buffer )
5077+
5078+ server .GetPendingPartialWithdrawals (rec , req )
5079+ require .Equal (t , http .StatusBadRequest , rec .Code )
5080+
5081+ var errResp struct {
5082+ Code int `json:"code"`
5083+ Message string `json:"message"`
5084+ }
5085+ require .NoError (t , json .Unmarshal (rec .Body .Bytes (), & errResp ))
5086+ require .Equal (t , "state_id is required in URL params" , errResp .Message )
5087+ })
5088+
5089+ t .Run ("optimistic node" , func (t * testing.T ) {
5090+ optimisticChainService := & chainMock.ChainService {
5091+ Optimistic : true ,
5092+ FinalizedRoots : map [[32 ]byte ]bool {},
5093+ }
5094+ optimisticServer := & Server {
5095+ Stater : server .Stater ,
5096+ OptimisticModeFetcher : optimisticChainService ,
5097+ FinalizationFetcher : optimisticChainService ,
5098+ }
5099+
5100+ req := httptest .NewRequest (http .MethodGet , "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals" , nil )
5101+ req .SetPathValue ("state_id" , "head" )
5102+ rec := httptest .NewRecorder ()
5103+ rec .Body = new (bytes.Buffer )
5104+
5105+ optimisticServer .GetPendingPartialWithdrawals (rec , req )
5106+ require .Equal (t , http .StatusOK , rec .Code )
5107+
5108+ var resp structs.GetPendingPartialWithdrawalsResponse
5109+ require .NoError (t , json .Unmarshal (rec .Body .Bytes (), & resp ))
5110+ require .Equal (t , true , resp .ExecutionOptimistic )
5111+ })
5112+
5113+ t .Run ("finalized node" , func (t * testing.T ) {
5114+ blockRoot , err := st .LatestBlockHeader ().HashTreeRoot ()
5115+ require .NoError (t , err )
5116+
5117+ finalizedChainService := & chainMock.ChainService {
5118+ Optimistic : false ,
5119+ FinalizedRoots : map [[32 ]byte ]bool {blockRoot : true },
5120+ }
5121+ finalizedServer := & Server {
5122+ Stater : server .Stater ,
5123+ OptimisticModeFetcher : finalizedChainService ,
5124+ FinalizationFetcher : finalizedChainService ,
5125+ }
5126+
5127+ req := httptest .NewRequest (http .MethodGet , "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals" , nil )
5128+ req .SetPathValue ("state_id" , "head" )
5129+ rec := httptest .NewRecorder ()
5130+ rec .Body = new (bytes.Buffer )
5131+
5132+ finalizedServer .GetPendingPartialWithdrawals (rec , req )
5133+ require .Equal (t , http .StatusOK , rec .Code )
5134+
5135+ var resp structs.GetPendingPartialWithdrawalsResponse
5136+ require .NoError (t , json .Unmarshal (rec .Body .Bytes (), & resp ))
5137+ require .Equal (t , true , resp .Finalized )
5138+ })
5139+ }
0 commit comments