diff --git a/block.go b/block.go index 2f37ee8d1..3dab884a1 100644 --- a/block.go +++ b/block.go @@ -42,8 +42,8 @@ var ( type BlockType byte const ( - BlockTypeBasic BlockType = 1 - BlockTypeValidation BlockType = 2 + BlockTypeBasic BlockType = 0 + BlockTypeValidation BlockType = 1 ) // EmptyBlockID returns an empty BlockID. diff --git a/nodeclient/apimodels/core.go b/nodeclient/apimodels/core.go index 59111db72..5f49ec8b1 100644 --- a/nodeclient/apimodels/core.go +++ b/nodeclient/apimodels/core.go @@ -315,21 +315,27 @@ type ( ValidatorStake iotago.BaseToken `serix:"3,mapKey=validatorStake"` // FixedCost is the fixed cost that the validator receives from the total pool reward. FixedCost iotago.Mana `serix:"4,mapKey=fixedCost"` + // Active indicates whether the validator was active recently, and would be considered during committee selection. + Active bool `serix:"5,mapKey=active"` // LatestSupportedProtocolVersion is the latest supported protocol version of the validator. - LatestSupportedProtocolVersion iotago.Version `serix:"5,mapKey=latestSupportedProtocolVersion"` + LatestSupportedProtocolVersion iotago.Version `serix:"6,mapKey=latestSupportedProtocolVersion"` } - // AccountStakingListResponse defines the response for the staking REST API call. - AccountStakingListResponse struct { - Stakers []*ValidatorResponse `serix:"0,mapKey=stakers"` + // ValidatorsResponse defines the response for the staking REST API call. + ValidatorsResponse struct { + Validators []*ValidatorResponse `serix:"0,mapKey=stakers"` + PageSize uint32 `serix:"1,mapKey=pageSize"` + Cursor string `serix:"2,mapKey=cursor,omitempty"` } // ManaRewardsResponse defines the response for the mana rewards REST API call. ManaRewardsResponse struct { - // EpochIndex is the epoch index for which the mana rewards are returned. - EpochIndex iotago.EpochIndex `serix:"0,mapKey=epochIndex"` - // The amount of totally available rewards the requested output may claim. - Rewards iotago.Mana `serix:"1,mapKey=rewards"` + // EpochStart is the starting epoch for the range for which the mana rewards are returned. + EpochStart iotago.EpochIndex `serix:"0,mapKey=epochIndexStart"` + // EpochEnd is the ending epoch for the range for which the mana rewards are returned, also the decay is only applied up to this point. + EpochEnd iotago.EpochIndex `serix:"1,mapKey=epochIndexEnd"` + // The amount of totally available rewards the requested output may claim, decayed up to EpochEnd (including). + Rewards iotago.Mana `serix:"2,mapKey=rewards"` } // CommitteeMemberResponse defines the response used in committee and staking response REST API calls. diff --git a/nodeclient/apimodels/core_test.go b/nodeclient/apimodels/core_test.go index 8f68d71d2..8c86dac34 100644 --- a/nodeclient/apimodels/core_test.go +++ b/nodeclient/apimodels/core_test.go @@ -282,26 +282,28 @@ func Test_CongestionResponse(t *testing.T) { func Test_AccountStakingListResponse(t *testing.T) { api := testAPI() - response := &apimodels.AccountStakingListResponse{ - Stakers: []*apimodels.ValidatorResponse{ + response := &apimodels.ValidatorsResponse{ + Validators: []*apimodels.ValidatorResponse{ { AccountID: iotago.AccountID{0xFF}, StakingEpochEnd: 0, PoolStake: 123, ValidatorStake: 456, FixedCost: 69, + Active: true, LatestSupportedProtocolVersion: 9, }, }, + Cursor: "0,1", + PageSize: 50, } jsonResponse, err := api.JSONEncode(response) require.NoError(t, err) - - expected := "{\"stakers\":[{\"accountId\":\"0xff00000000000000000000000000000000000000000000000000000000000000\",\"stakingEpochEnd\":\"0\",\"poolStake\":\"123\",\"validatorStake\":\"456\",\"fixedCost\":\"69\",\"latestSupportedProtocolVersion\":9}]}" + expected := "{\"stakers\":[{\"accountId\":\"0xff00000000000000000000000000000000000000000000000000000000000000\",\"stakingEpochEnd\":\"0\",\"poolStake\":\"123\",\"validatorStake\":\"456\",\"fixedCost\":\"69\",\"active\":true,\"latestSupportedProtocolVersion\":9}],\"pageSize\":50,\"cursor\":\"0,1\"}" require.Equal(t, expected, string(jsonResponse)) - decoded := new(apimodels.AccountStakingListResponse) + decoded := new(apimodels.ValidatorsResponse) require.NoError(t, api.JSONDecode(jsonResponse, decoded)) require.EqualValues(t, response, decoded) } @@ -310,14 +312,15 @@ func Test_ManaRewardsResponse(t *testing.T) { api := testAPI() response := &apimodels.ManaRewardsResponse{ - EpochIndex: 123, + EpochStart: 123, + EpochEnd: 133, Rewards: 456, } jsonResponse, err := api.JSONEncode(response) require.NoError(t, err) - expected := "{\"epochIndex\":\"123\",\"rewards\":\"456\"}" + expected := "{\"epochIndexStart\":\"123\",\"epochIndexEnd\":\"133\",\"rewards\":\"456\"}" require.Equal(t, expected, string(jsonResponse)) decoded := new(apimodels.ManaRewardsResponse) diff --git a/nodeclient/http_api_client.go b/nodeclient/http_api_client.go index 6e84cc787..f59751cf4 100644 --- a/nodeclient/http_api_client.go +++ b/nodeclient/http_api_client.go @@ -35,8 +35,41 @@ const ( // GET returns the node info. RouteInfo = "/api/core/v3/info" + // RouteCongestion is the route for getting congestion details for the account. + // GET returns the congestion details for the account. + // MIMEApplicationJSON => json. + // MIMEVendorIOTASerializer => bytes. + RouteCongestion = "/api/core/v3/accounts/%s/congestion" + + // RouteRewards is the route for getting the rewards for staking or delegation based on the provided output. + // Rewards are decayed up to returned epochEnd index. + // GET returns the rewards for the output. + // MIMEApplicationJSON => json. + // MIMEVendorIOTASerializer => bytes. + RouteRewards = "/api/core/v3/rewards/%s" + + // RouteValidators is the route for getting the information about current registered validators. + // GET returns the paginated information about about registered validators. + // MIMEApplicationJSON => json. + // MIMEVendorIOTASerializer => bytes. + RouteValidators = "/api/core/v3/validators" + + // RouteValidatorsAccount is the route for getting validator by its accountID. + // GET returns the account details. + // MIMEApplicationJSON => json. + // MIMEVendorIOTASerializer => bytes. + RouteValidatorsAccount = "/api/core/v3/validators/%s" + + // RouteCommittee is the route for getting the information about the current committee. + // GET returns the information about the current committee. + // MIMEApplicationJSON => json. + // MIMEVendorIOTASerializer => bytes. + RouteCommittee = "/api/core/v3/committee" + // RouteBlockIssuance is the route for getting all needed information for block creation. // GET returns the data needed toa attach block. + // MIMEApplicationJSON => json. + // MIMEVendorIOTASerializer => bytes. RouteBlockIssuance = "/api/core/v3/blocks/issuance" // RouteBlock is the route for getting a block by its ID. @@ -64,6 +97,8 @@ const ( // RouteTransactionsIncludedBlockMetadata is the route for getting the block metadata that was first confirmed in the ledger for a given transaction ID. // GET returns block metadata (including info about "promotion/reattachment needed"). + // MIMEApplicationJSON => json. + // MIMEVendorIOTASerializer => bytes. RouteTransactionsIncludedBlockMetadata = "/api/core/v3/transactions/%s/included-block/metadata" // RouteCommitmentByID is the route for getting a commitment by its ID. @@ -321,6 +356,65 @@ func (client *Client) BlockIssuance(ctx context.Context) (*apimodels.IssuanceBlo return res, nil } +func (client *Client) Congestion(ctx context.Context, accountID iotago.AccountID) (*apimodels.CongestionResponse, error) { + res := new(apimodels.CongestionResponse) + query := fmt.Sprintf(RouteCongestion, hexutil.EncodeHex(accountID[:])) + //nolint:bodyclose + if _, err := client.Do(ctx, http.MethodGet, query, nil, res); err != nil { + return nil, err + } + + return res, nil +} + +func (client *Client) Rewards(ctx context.Context, outputID iotago.OutputID) (*apimodels.ManaRewardsResponse, error) { + res := &apimodels.ManaRewardsResponse{} + query := fmt.Sprintf(RouteRewards, hexutil.EncodeHex(outputID[:])) + //nolint:bodyclose + if _, err := client.Do(ctx, http.MethodGet, query, nil, res); err != nil { + return nil, err + } + + return res, nil +} + +func (client *Client) Validators(ctx context.Context) (*apimodels.ValidatorsResponse, error) { + res := &apimodels.ValidatorsResponse{} + //nolint:bodyclose + if _, err := client.Do(ctx, http.MethodGet, RouteValidators, nil, res); err != nil { + return nil, err + } + + return res, nil +} + +func (client *Client) StakingAccount(ctx context.Context, accountID iotago.AccountID) (*apimodels.ValidatorResponse, error) { + res := &apimodels.ValidatorResponse{} + query := fmt.Sprintf(RouteValidatorsAccount, hexutil.EncodeHex(accountID[:])) + //nolint:bodyclose + if _, err := client.Do(ctx, http.MethodGet, query, nil, res); err != nil { + return nil, err + } + + return res, nil +} + +func (client *Client) Committee(ctx context.Context, optEpochIndex ...iotago.EpochIndex) (*apimodels.CommitteeResponse, error) { + query := RouteCommittee + if len(optEpochIndex) > 0 { + query += fmt.Sprintf("?epochIndex=%d", optEpochIndex[0]) + } + fmt.Printf("query: %s\n", query) + + res := &apimodels.CommitteeResponse{} + //nolint:bodyclose + if _, err := client.Do(ctx, http.MethodGet, query, nil, res); err != nil { + return nil, err + } + + return res, nil +} + // NodeSupportsRoute gets the routes of the node and checks if the given route is enabled. func (client *Client) NodeSupportsRoute(ctx context.Context, route string) (bool, error) { routes, err := client.Routes(ctx) diff --git a/nodeclient/http_api_client_test.go b/nodeclient/http_api_client_test.go index 64d646bb8..43d8b0ff2 100644 --- a/nodeclient/http_api_client_test.go +++ b/nodeclient/http_api_client_test.go @@ -197,6 +197,123 @@ func TestClient_BlockIssuance(t *testing.T) { require.EqualValues(t, originRes, res) } +func TestClient_Congestion(t *testing.T) { + defer gock.Off() + + accID := tpkg.RandAccountID() + + originRes := &apimodels.CongestionResponse{ + SlotIndex: iotago.SlotIndex(20), + Ready: true, + ReferenceManaCost: iotago.Mana(1000), + BlockIssuanceCredits: iotago.BlockIssuanceCredits(1000), + } + + mockGetJSON(fmt.Sprintf(nodeclient.RouteCongestion, accID.ToHex()), 200, originRes) + + nodeAPI := nodeClient(t) + res, err := nodeAPI.Congestion(context.Background(), accID) + require.NoError(t, err) + require.EqualValues(t, originRes, res) +} + +func TestClient_Rewards(t *testing.T) { + defer gock.Off() + + outID := tpkg.RandOutputID(1) + + originRes := &apimodels.ManaRewardsResponse{ + EpochStart: iotago.EpochIndex(20), + EpochEnd: iotago.EpochIndex(30), + Rewards: iotago.Mana(1000), + } + + mockGetJSON(fmt.Sprintf(nodeclient.RouteRewards, outID.ToHex()), 200, originRes) + + nodeAPI := nodeClient(t) + res, err := nodeAPI.Rewards(context.Background(), outID) + require.NoError(t, err) + require.EqualValues(t, originRes, res) +} + +func TestClient_Validators(t *testing.T) { + defer gock.Off() + + originRes := &apimodels.ValidatorsResponse{Validators: []*apimodels.ValidatorResponse{ + { + AccountID: tpkg.RandAccountID(), + StakingEpochEnd: iotago.EpochIndex(123), + PoolStake: iotago.BaseToken(100), + ValidatorStake: iotago.BaseToken(10), + FixedCost: iotago.Mana(10), + Active: true, + LatestSupportedProtocolVersion: 1, + }, + { + AccountID: tpkg.RandAccountID(), + StakingEpochEnd: iotago.EpochIndex(124), + PoolStake: iotago.BaseToken(1000), + ValidatorStake: iotago.BaseToken(100), + FixedCost: iotago.Mana(20), + Active: true, + LatestSupportedProtocolVersion: 1, + }, + }} + + mockGetJSON(nodeclient.RouteValidators, 200, originRes) + + nodeAPI := nodeClient(t) + res, err := nodeAPI.Validators(context.Background()) + require.NoError(t, err) + require.EqualValues(t, originRes, res) +} + +func TestClient_StakingByAccountID(t *testing.T) { + defer gock.Off() + + accID := tpkg.RandAccountID() + originRes := &apimodels.ValidatorResponse{ + AccountID: accID, + StakingEpochEnd: iotago.EpochIndex(123), + PoolStake: iotago.BaseToken(100), + ValidatorStake: iotago.BaseToken(10), + FixedCost: iotago.Mana(10), + Active: true, + LatestSupportedProtocolVersion: 1, + } + + mockGetJSON(fmt.Sprintf(nodeclient.RouteValidatorsAccount, accID.ToHex()), 200, originRes) + + nodeAPI := nodeClient(t) + res, err := nodeAPI.StakingAccount(context.Background(), accID) + require.NoError(t, err) + require.EqualValues(t, originRes, res) +} + +func TestClient_Committee(t *testing.T) { + defer gock.Off() + + originRes := &apimodels.CommitteeResponse{ + EpochIndex: iotago.EpochIndex(123), + TotalStake: 1000_1000, + TotalValidatorStake: 100_000, + Committee: []*apimodels.CommitteeMemberResponse{ + { + AccountID: tpkg.RandAccountID(), + PoolStake: 1000_000, + ValidatorStake: 100_000, + FixedCost: iotago.Mana(100), + }, + }, + } + + mockGetJSON(nodeclient.RouteCommittee, 200, originRes) + nodeAPI := nodeClient(t) + res, err := nodeAPI.Committee(context.Background()) + require.NoError(t, err) + require.EqualValues(t, originRes, res) +} + func TestClient_SubmitBlock(t *testing.T) { defer gock.Off()