diff --git a/components/dashboard/explorer_routes.go b/components/dashboard/explorer_routes.go index 4918fc2f8..f5a73d25d 100644 --- a/components/dashboard/explorer_routes.go +++ b/components/dashboard/explorer_routes.go @@ -263,6 +263,10 @@ func getSlotDetailsByID(c echo.Context) error { return err } + if commitment.ID() != commitmentID { + return ierrors.Errorf("commitment in the store for slot %d does not match the given commitmentID (%s != %s)", commitmentID.Slot(), commitment.ID(), commitmentID) + } + diffs, err := deps.Protocol.MainEngineInstance().Ledger.SlotDiffs(commitmentID.Slot()) if err != nil { return err diff --git a/components/inx/server_commitments.go b/components/inx/server_commitments.go index a88448dd8..a071a5116 100644 --- a/components/inx/server_commitments.go +++ b/components/inx/server_commitments.go @@ -15,6 +15,10 @@ import ( ) func inxCommitment(commitment *model.Commitment) *inx.Commitment { + if commitment == nil { + return nil + } + return &inx.Commitment{ CommitmentId: inx.NewCommitmentId(commitment.ID()), Commitment: &inx.RawCommitment{ diff --git a/components/inx/server_utxo.go b/components/inx/server_utxo.go index d1e87561e..fe732b384 100644 --- a/components/inx/server_utxo.go +++ b/components/inx/server_utxo.go @@ -30,7 +30,10 @@ func NewLedgerOutput(o *utxoledger.Output) (*inx.LedgerOutput, error) { } includedSlot := o.SlotBooked() - if includedSlot > 0 && includedSlot <= latestCommitment.Slot() { + if includedSlot > 0 && + includedSlot <= latestCommitment.Slot() && + includedSlot >= deps.Protocol.CommittedAPI().ProtocolParameters().GenesisSlot() { + includedCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(includedSlot) if err != nil { return nil, ierrors.Wrapf(err, "failed to load commitment with slot: %d", includedSlot) @@ -55,7 +58,10 @@ func NewLedgerSpent(s *utxoledger.Spent) (*inx.LedgerSpent, error) { latestCommitment := deps.Protocol.MainEngineInstance().SyncManager.LatestCommitment() spentSlot := s.SlotSpent() - if spentSlot > 0 && spentSlot <= latestCommitment.Slot() { + if spentSlot > 0 && + spentSlot <= latestCommitment.Slot() && + spentSlot >= deps.Protocol.CommittedAPI().ProtocolParameters().GenesisSlot() { + spentCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(spentSlot) if err != nil { return nil, ierrors.Wrapf(err, "failed to load commitment with slot: %d", spentSlot) diff --git a/components/restapi/core/accounts.go b/components/restapi/core/accounts.go index 381c0105b..9b0eaffee 100644 --- a/components/restapi/core/accounts.go +++ b/components/restapi/core/accounts.go @@ -22,7 +22,19 @@ func congestionForAccountID(c echo.Context) (*apimodels.CongestionResponse, erro return nil, err } + commitmentID, err := httpserver.ParseCommitmentIDQueryParam(c, restapipkg.ParameterCommitmentID) + if err != nil { + return nil, err + } + commitment := deps.Protocol.MainEngineInstance().SyncManager.LatestCommitment() + if commitmentID != iotago.EmptyCommitmentID { + // a commitment ID was provided, so we use the commitment for that ID + commitment, err = getCommitmentByID(commitmentID, commitment) + if err != nil { + return nil, err + } + } acc, exists, err := deps.Protocol.MainEngineInstance().Ledger.Account(accountID, commitment.Slot()) if err != nil { diff --git a/components/restapi/core/commitment.go b/components/restapi/core/commitment.go index 48f2598d2..2990f9b30 100644 --- a/components/restapi/core/commitment.go +++ b/components/restapi/core/commitment.go @@ -4,39 +4,59 @@ import ( "github.com/labstack/echo/v4" "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/inx-app/pkg/httpserver" - restapipkg "github.com/iotaledger/iota-core/pkg/restapi" + "github.com/iotaledger/iota-core/pkg/model" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/nodeclient/apimodels" ) -func indexByCommitmentID(c echo.Context) (iotago.SlotIndex, error) { - commitmentID, err := httpserver.ParseCommitmentIDParam(c, restapipkg.ParameterCommitmentID) - if err != nil { - return iotago.SlotIndex(0), ierrors.Wrapf(err, "failed to parse commitment ID %s", c.Param(restapipkg.ParameterCommitmentID)) +func getCommitmentBySlot(slot iotago.SlotIndex, latestCommitment ...*model.Commitment) (*model.Commitment, error) { + var latest *model.Commitment + if len(latestCommitment) > 0 { + latest = latestCommitment[0] + } else { + latest = deps.Protocol.MainEngineInstance().SyncManager.LatestCommitment() } - return commitmentID.Slot(), nil -} + if slot > latest.Slot() { + return nil, ierrors.Wrapf(echo.ErrBadRequest, "commitment is from a future slot (%d > %d)", slot, latest.Slot()) + } -func getCommitmentDetails(index iotago.SlotIndex) (*iotago.Commitment, error) { - commitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(index) + commitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(slot) if err != nil { - return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment %d: %s", index, err) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment, slot: %d, error: %w", slot, err) } - return commitment.Commitment(), nil + return commitment, nil } -func getUTXOChanges(slot iotago.SlotIndex) (*apimodels.UTXOChangesResponse, error) { - diffs, err := deps.Protocol.MainEngineInstance().Ledger.SlotDiffs(slot) +func getCommitmentByID(commitmentID iotago.CommitmentID, latestCommitment ...*model.Commitment) (*model.Commitment, error) { + var latest *model.Commitment + if len(latestCommitment) > 0 { + latest = latestCommitment[0] + } else { + latest = deps.Protocol.MainEngineInstance().SyncManager.LatestCommitment() + } + + if commitmentID.Slot() > latest.Slot() { + return nil, ierrors.Wrapf(echo.ErrBadRequest, "commitment ID (%s) is from a future slot (%d > %d)", commitmentID, commitmentID.Slot(), latest.Slot()) + } + + commitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(commitmentID.Slot()) if err != nil { - return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get slot diffs %d: %s", slot, err) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment, commitmentID: %s, slot: %d, error: %w", commitmentID, commitmentID.Slot(), err) + } + + if commitment.ID() != commitmentID { + return nil, ierrors.Wrapf(echo.ErrBadRequest, "commitment in the store for slot %d does not match the given commitmentID (%s != %s)", commitmentID.Slot(), commitment.ID(), commitmentID) } - commitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(diffs.Slot) + return commitment, nil +} + +func getUTXOChanges(commitmentID iotago.CommitmentID) (*apimodels.UTXOChangesResponse, error) { + diffs, err := deps.Protocol.MainEngineInstance().Ledger.SlotDiffs(commitmentID.Slot()) if err != nil { - return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment %d: %s", diffs.Slot, err) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get slot diffs, commitmentID: %s, slot: %d, error: %w", commitmentID, commitmentID.Slot(), err) } createdOutputs := make(iotago.OutputIDs, len(diffs.Outputs)) @@ -51,7 +71,7 @@ func getUTXOChanges(slot iotago.SlotIndex) (*apimodels.UTXOChangesResponse, erro } return &apimodels.UTXOChangesResponse{ - CommitmentID: commitment.ID(), + CommitmentID: commitmentID, CreatedOutputs: createdOutputs, ConsumedOutputs: consumedOutputs, }, nil diff --git a/components/restapi/core/component.go b/components/restapi/core/component.go index 6785dc462..4494b3f1c 100644 --- a/components/restapi/core/component.go +++ b/components/restapi/core/component.go @@ -112,7 +112,7 @@ const ( RouteCommitmentByIndexUTXOChanges = "/commitments/by-index/:" + restapipkg.ParameterSlotIndex + "/utxo-changes" // RouteCongestion is the route for getting the current congestion state and all account related useful details as block issuance credits. - // GET returns the congestion state related to the specified account. + // GET returns the congestion state related to the specified account. (optional query parameters: "commitmentID" to specify the used commitment) // MIMEApplicationJSON => json. // MIMEApplicationVendorIOTASerializerV2 => bytes. RouteCongestion = "/accounts/:" + restapipkg.ParameterAccountID + "/congestion" @@ -235,26 +235,32 @@ func configure() error { }, checkNodeSynced()) routeGroup.GET(RouteCommitmentByID, func(c echo.Context) error { - index, err := indexByCommitmentID(c) + commitmentID, err := httpserver.ParseCommitmentIDParam(c, restapipkg.ParameterCommitmentID) if err != nil { return err } - commitment, err := getCommitmentDetails(index) + commitment, err := getCommitmentByID(commitmentID) if err != nil { return err } - return responseByHeader(c, commitment) + return responseByHeader(c, commitment.Commitment()) }) routeGroup.GET(RouteCommitmentByIDUTXOChanges, func(c echo.Context) error { - index, err := indexByCommitmentID(c) + commitmentID, err := httpserver.ParseCommitmentIDParam(c, restapipkg.ParameterCommitmentID) if err != nil { return err } - resp, err := getUTXOChanges(index) + // load the commitment to check if it matches the given commitmentID + commitment, err := getCommitmentByID(commitmentID) + if err != nil { + return err + } + + resp, err := getUTXOChanges(commitment.ID()) if err != nil { return err } @@ -268,21 +274,26 @@ func configure() error { return err } - resp, err := getCommitmentDetails(index) + commitment, err := getCommitmentBySlot(index) if err != nil { return err } - return responseByHeader(c, resp) + return responseByHeader(c, commitment.Commitment()) }) routeGroup.GET(RouteCommitmentByIndexUTXOChanges, func(c echo.Context) error { - index, err := httpserver.ParseSlotParam(c, restapipkg.ParameterSlotIndex) + slot, err := httpserver.ParseSlotParam(c, restapipkg.ParameterSlotIndex) + if err != nil { + return err + } + + commitment, err := getCommitmentBySlot(slot) if err != nil { return err } - resp, err := getUTXOChanges(index) + resp, err := getUTXOChanges(commitment.ID()) if err != nil { return err } diff --git a/components/restapi/core/utxo.go b/components/restapi/core/utxo.go index 2fd9a7fad..cec4f033e 100644 --- a/components/restapi/core/utxo.go +++ b/components/restapi/core/utxo.go @@ -93,7 +93,8 @@ func newOutputMetadataResponse(output *utxoledger.Output) (*apimodels.OutputMeta } includedSlotIndex := output.SlotBooked() - if includedSlotIndex <= latestCommitment.Slot() { + genesisSlot := deps.Protocol.MainEngineInstance().CommittedAPI().ProtocolParameters().GenesisSlot() + if includedSlotIndex <= latestCommitment.Slot() && includedSlotIndex >= genesisSlot { includedCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(includedSlotIndex) if err != nil { return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment with index %d: %s", includedSlotIndex, err) @@ -117,7 +118,8 @@ func newSpentMetadataResponse(spent *utxoledger.Spent) (*apimodels.OutputMetadat } includedSlotIndex := spent.Output().SlotBooked() - if includedSlotIndex <= latestCommitment.Slot() { + genesisSlot := deps.Protocol.MainEngineInstance().CommittedAPI().ProtocolParameters().GenesisSlot() + if includedSlotIndex <= latestCommitment.Slot() && includedSlotIndex >= genesisSlot { includedCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(includedSlotIndex) if err != nil { return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment with index %d: %s", includedSlotIndex, err) @@ -126,7 +128,7 @@ func newSpentMetadataResponse(spent *utxoledger.Spent) (*apimodels.OutputMetadat } spentSlotIndex := spent.SlotSpent() - if spentSlotIndex <= latestCommitment.Slot() { + if spentSlotIndex <= latestCommitment.Slot() && spentSlotIndex >= genesisSlot { spentCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(spentSlotIndex) if err != nil { return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment with index %d: %s", spentSlotIndex, err) diff --git a/pkg/protocol/engine/committed_slot_api.go b/pkg/protocol/engine/committed_slot_api.go index 2a2871b24..ad70e4785 100644 --- a/pkg/protocol/engine/committed_slot_api.go +++ b/pkg/protocol/engine/committed_slot_api.go @@ -30,6 +30,10 @@ func (c *CommittedSlotAPI) Commitment() (commitment *model.Commitment, err error return nil, ierrors.Wrapf(err, "failed to load commitment for slot %d", c.CommitmentID) } + if commitment.ID() != c.CommitmentID { + return nil, ierrors.Errorf("commitment in the store does not match the given commitmentID (%s != %s)", commitment.ID(), c.CommitmentID) + } + return commitment, nil }