Skip to content

Commit

Permalink
feat: store state history in db (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
gusin13 authored Jan 9, 2025
1 parent a54dce1 commit e9875a9
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 43 deletions.
10 changes: 10 additions & 0 deletions e2etest/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,14 @@ func TestStakingEarlyUnbonding(t *testing.T) {

// Consume unbonding staking event emitted by Indexer
tm.CheckNextUnbondingStakingEvent(t, stakingMsgTxHash.String())

// Verify state history in Indexer DB
delegation, err := tm.DbClient.GetBTCDelegationByStakingTxHash(ctx, stakingMsgTxHash.String())
require.NoError(t, err)
require.NotEmpty(t, delegation.StateHistory, "State history should not be empty")
require.Equal(t, delegation.StateHistory[0].State, types.StatePending)
require.Equal(t, delegation.StateHistory[1].State, types.StateVerified)
require.Equal(t, delegation.StateHistory[2].State, types.StateActive)
require.Equal(t, delegation.StateHistory[3].State, types.StateUnbonding)
require.Equal(t, delegation.StateHistory[3].SubState, expectedSubState)
}
78 changes: 75 additions & 3 deletions internal/db/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@ import (
"go.mongodb.org/mongo-driver/mongo"
)

// UpdateOption is a function that modifies update options
type UpdateOption func(*updateOptions)

// updateOptions holds all possible optional parameters
type updateOptions struct {
subState *types.DelegationSubState
bbnHeight *int64
btcHeight *int64
}

// WithSubState sets the sub-state option
func WithSubState(subState types.DelegationSubState) UpdateOption {
return func(opts *updateOptions) {
opts.subState = &subState
}
}

// WithBbnHeight sets the BBN height option
func WithBbnHeight(height int64) UpdateOption {
return func(opts *updateOptions) {
opts.bbnHeight = &height
}
}

// WithBtcHeight sets the BTC height option
func WithBtcHeight(height int64) UpdateOption {
return func(opts *updateOptions) {
opts.btcHeight = &height
}
}

func (db *Database) SaveNewBTCDelegation(
ctx context.Context, delegationDoc *model.BTCDelegationDetails,
) error {
Expand Down Expand Up @@ -40,7 +71,7 @@ func (db *Database) UpdateBTCDelegationState(
stakingTxHash string,
qualifiedPreviousStates []types.DelegationState,
newState types.DelegationState,
newSubState *types.DelegationSubState,
opts ...UpdateOption, // Can pass multiple optional parameters
) error {
if len(qualifiedPreviousStates) == 0 {
return fmt.Errorf("qualified previous states array cannot be empty")
Expand All @@ -51,6 +82,15 @@ func (db *Database) UpdateBTCDelegationState(
qualifiedStateStrs[i] = state.String()
}

options := &updateOptions{}
for _, opt := range opts {
opt(options)
}

stateRecord := model.StateRecord{
State: newState,
}

filter := bson.M{
"_id": stakingTxHash,
"state": bson.M{"$in": qualifiedStateStrs},
Expand All @@ -60,12 +100,24 @@ func (db *Database) UpdateBTCDelegationState(
"state": newState.String(),
}

if newSubState != nil {
updateFields["sub_state"] = newSubState.String()
if options.bbnHeight != nil {
stateRecord.BbnHeight = *options.bbnHeight
}

if options.btcHeight != nil {
stateRecord.BtcHeight = *options.btcHeight
}

if options.subState != nil {
stateRecord.SubState = *options.subState
updateFields["sub_state"] = options.subState.String()
}

update := bson.M{
"$set": updateFields,
"$push": bson.M{
"state_history": stateRecord,
},
}

res := db.client.Database(db.dbName).
Expand Down Expand Up @@ -98,13 +150,20 @@ func (db *Database) GetBTCDelegationState(
func (db *Database) UpdateBTCDelegationDetails(
ctx context.Context,
stakingTxHash string,
bbnBlockHeight int64,
details *model.BTCDelegationDetails,
) error {
updateFields := bson.M{}

var stateRecord *model.StateRecord

// Only add fields to updateFields if they are not empty
if details.State.String() != "" {
updateFields["state"] = details.State.String()
stateRecord = &model.StateRecord{
State: details.State,
BbnHeight: bbnBlockHeight,
}
}
if details.StartHeight != 0 {
updateFields["start_height"] = details.StartHeight
Expand All @@ -118,6 +177,10 @@ func (db *Database) UpdateBTCDelegationDetails(
filter := bson.M{"_id": stakingTxHash}
update := bson.M{"$set": updateFields}

if stateRecord != nil {
update["$push"] = bson.M{"state_history": stateRecord}
}

res, err := db.client.Database(db.dbName).
Collection(model.BTCDelegationDetailsCollection).
UpdateOne(ctx, filter, update)
Expand Down Expand Up @@ -183,15 +246,24 @@ func (db *Database) UpdateDelegationsStateByFinalityProvider(
ctx context.Context,
fpBTCPKHex string,
newState types.DelegationState,
bbnBlockHeight int64,
) error {
filter := bson.M{
"finality_provider_btc_pks_hex": fpBTCPKHex,
}

stateRecord := model.StateRecord{
State: newState,
BbnHeight: bbnBlockHeight,
}

update := bson.M{
"$set": bson.M{
"state": newState.String(),
},
"$push": bson.M{
"state_history": stateRecord,
},
}

result, err := db.client.Database(db.dbName).
Expand Down
16 changes: 10 additions & 6 deletions internal/db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,20 @@ type DbInterface interface {
ctx context.Context, delegationDoc *model.BTCDelegationDetails,
) error
/**
* SaveBTCDelegationStateUpdate saves a BTC delegation state update to the database.
* UpdateBTCDelegationState updates a BTC delegation state in the database.
* @param ctx The context
* @param delegationDoc The BTC delegation details
* @param stakingTxHash The staking transaction hash
* @param qualifiedPreviousStates The previous states that qualify for this update
* @param newState The new state to update to
* @param opts Optional parameters for the update
* @return An error if the operation failed
*/
UpdateBTCDelegationState(
ctx context.Context,
stakingTxHash string,
qualifiedPreviousStates []types.DelegationState,
newState types.DelegationState,
newSubState *types.DelegationSubState,
opts ...UpdateOption,
) error
/**
* SaveBTCDelegationUnbondingCovenantSignature saves a BTC delegation
Expand All @@ -127,11 +130,12 @@ type DbInterface interface {
* UpdateBTCDelegationDetails updates the BTC delegation details.
* @param ctx The context
* @param stakingTxHash The staking tx hash
* @param bbnBlockHeight The Babylon block height
* @param details The BTC delegation details to update
* @return An error if the operation failed
*/
UpdateBTCDelegationDetails(
ctx context.Context, stakingTxHash string, details *model.BTCDelegationDetails,
ctx context.Context, stakingTxHash string, bbnBlockHeight int64, details *model.BTCDelegationDetails,
) error
/**
* GetBTCDelegationByStakingTxHash retrieves the BTC delegation details by the staking tx hash.
Expand All @@ -148,11 +152,11 @@ type DbInterface interface {
* @param ctx The context
* @param fpBtcPkHex The finality provider public key
* @param newState The new state
* @param qualifiedStates The qualified states
* @param bbnBlockHeight The Babylon block height
* @return An error if the operation failed
*/
UpdateDelegationsStateByFinalityProvider(
ctx context.Context, fpBtcPkHex string, newState types.DelegationState,
ctx context.Context, fpBtcPkHex string, newState types.DelegationState, bbnBlockHeight int64,
) error
/**
* GetDelegationsByFinalityProvider retrieves the BTC delegations by the finality provider public key.
Expand Down
14 changes: 14 additions & 0 deletions internal/db/model/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ type SlashingTx struct {
SpendingHeight uint32 `bson:"spending_height"`
}

type StateRecord struct {
State types.DelegationState `bson:"state"`
SubState types.DelegationSubState `bson:"sub_state,omitempty"`
BbnHeight int64 `bson:"bbn_height,omitempty"` // Babylon block height when applicable
BtcHeight int64 `bson:"btc_height,omitempty"` // Bitcoin block height when applicable
}

type BTCDelegationDetails struct {
StakingTxHashHex string `bson:"_id"` // Primary key
StakingTxHex string `bson:"staking_tx_hex"`
Expand All @@ -39,6 +46,7 @@ type BTCDelegationDetails struct {
EndHeight uint32 `bson:"end_height"`
State types.DelegationState `bson:"state"`
SubState types.DelegationSubState `bson:"sub_state,omitempty"`
StateHistory []StateRecord `bson:"state_history"`
ParamsVersion uint32 `bson:"params_version"`
UnbondingTime uint32 `bson:"unbonding_time"`
UnbondingTx string `bson:"unbonding_tx"`
Expand Down Expand Up @@ -118,6 +126,12 @@ func FromEventBTCDelegationCreated(
Height: bbnBlockHeight,
Timestamp: bbnBlockTime,
},
StateHistory: []StateRecord{
{
State: types.StatePending,
BbnHeight: bbnBlockHeight,
},
},
}, nil
}

Expand Down
21 changes: 12 additions & 9 deletions internal/services/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (s *Service) processCovenantSignatureReceivedEvent(
}

func (s *Service) processCovenantQuorumReachedEvent(
ctx context.Context, event abcitypes.Event,
ctx context.Context, event abcitypes.Event, bbnBlockHeight int64,
) *types.Error {
covenantQuorumReachedEvent, err := parseEvent[*bbntypes.EventCovenantQuorumReached](
EventCovenantQuorumReached, event,
Expand Down Expand Up @@ -186,7 +186,7 @@ func (s *Service) processCovenantQuorumReachedEvent(
covenantQuorumReachedEvent.StakingTxHash,
types.QualifiedStatesForCovenantQuorumReached(covenantQuorumReachedEvent.NewState),
newState,
nil,
db.WithBbnHeight(bbnBlockHeight),
); dbErr != nil {
return types.NewError(
http.StatusInternalServerError,
Expand All @@ -199,7 +199,7 @@ func (s *Service) processCovenantQuorumReachedEvent(
}

func (s *Service) processBTCDelegationInclusionProofReceivedEvent(
ctx context.Context, event abcitypes.Event,
ctx context.Context, event abcitypes.Event, bbnBlockHeight int64,
) *types.Error {
inclusionProofEvent, err := parseEvent[*bbntypes.EventBTCDelegationInclusionProofReceived](
EventBTCDelegationInclusionProofReceived, event,
Expand Down Expand Up @@ -261,6 +261,7 @@ func (s *Service) processBTCDelegationInclusionProofReceivedEvent(
if dbErr := s.db.UpdateBTCDelegationDetails(
ctx,
inclusionProofEvent.StakingTxHash,
bbnBlockHeight,
model.FromEventBTCDelegationInclusionProofReceived(inclusionProofEvent),
); dbErr != nil {
return types.NewError(
Expand All @@ -274,7 +275,7 @@ func (s *Service) processBTCDelegationInclusionProofReceivedEvent(
}

func (s *Service) processBTCDelegationUnbondedEarlyEvent(
ctx context.Context, event abcitypes.Event,
ctx context.Context, event abcitypes.Event, bbnBlockHeight int64,
) *types.Error {
unbondedEarlyEvent, err := parseEvent[*bbntypes.EventBTCDelgationUnbondedEarly](
EventBTCDelgationUnbondedEarly,
Expand Down Expand Up @@ -349,7 +350,8 @@ func (s *Service) processBTCDelegationUnbondedEarlyEvent(
unbondedEarlyEvent.StakingTxHash,
types.QualifiedStatesForUnbondedEarly(),
types.StateUnbonding,
&subState,
db.WithSubState(subState),
db.WithBbnHeight(bbnBlockHeight),
); err != nil {
return types.NewError(
http.StatusInternalServerError,
Expand All @@ -362,7 +364,7 @@ func (s *Service) processBTCDelegationUnbondedEarlyEvent(
}

func (s *Service) processBTCDelegationExpiredEvent(
ctx context.Context, event abcitypes.Event,
ctx context.Context, event abcitypes.Event, bbnBlockHeight int64,
) *types.Error {
expiredEvent, err := parseEvent[*bbntypes.EventBTCDelegationExpired](
EventBTCDelegationExpired,
Expand Down Expand Up @@ -417,7 +419,8 @@ func (s *Service) processBTCDelegationExpiredEvent(
delegation.StakingTxHashHex,
types.QualifiedStatesForExpired(),
types.StateUnbonding,
&subState,
db.WithSubState(subState),
db.WithBbnHeight(bbnBlockHeight),
); err != nil {
return types.NewError(
http.StatusInternalServerError,
Expand All @@ -430,7 +433,7 @@ func (s *Service) processBTCDelegationExpiredEvent(
}

func (s *Service) processSlashedFinalityProviderEvent(
ctx context.Context, event abcitypes.Event,
ctx context.Context, event abcitypes.Event, bbnBlockHeight int64,
) *types.Error {
slashedFinalityProviderEvent, err := parseEvent[*ftypes.EventSlashedFinalityProvider](
EventSlashedFinalityProvider,
Expand All @@ -453,7 +456,7 @@ func (s *Service) processSlashedFinalityProviderEvent(
fpBTCPKHex := evidence.FpBtcPk.MarshalHex()

if dbErr := s.db.UpdateDelegationsStateByFinalityProvider(
ctx, fpBTCPKHex, types.StateSlashed,
ctx, fpBTCPKHex, types.StateSlashed, bbnBlockHeight,
); dbErr != nil {
return types.NewError(
http.StatusInternalServerError,
Expand Down
10 changes: 5 additions & 5 deletions internal/services/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,22 @@ func (s *Service) processEvent(
err = s.processNewBTCDelegationEvent(ctx, bbnEvent, blockHeight)
case EventCovenantQuorumReached:
log.Debug().Msg("Processing covenant quorum reached event")
err = s.processCovenantQuorumReachedEvent(ctx, bbnEvent)
err = s.processCovenantQuorumReachedEvent(ctx, bbnEvent, blockHeight)
case EventCovenantSignatureReceived:
log.Debug().Msg("Processing covenant signature received event")
err = s.processCovenantSignatureReceivedEvent(ctx, bbnEvent)
case EventBTCDelegationInclusionProofReceived:
log.Debug().Msg("Processing BTC delegation inclusion proof received event")
err = s.processBTCDelegationInclusionProofReceivedEvent(ctx, bbnEvent)
err = s.processBTCDelegationInclusionProofReceivedEvent(ctx, bbnEvent, blockHeight)
case EventBTCDelgationUnbondedEarly:
log.Debug().Msg("Processing BTC delegation unbonded early event")
err = s.processBTCDelegationUnbondedEarlyEvent(ctx, bbnEvent)
err = s.processBTCDelegationUnbondedEarlyEvent(ctx, bbnEvent, blockHeight)
case EventBTCDelegationExpired:
log.Debug().Msg("Processing BTC delegation expired event")
err = s.processBTCDelegationExpiredEvent(ctx, bbnEvent)
err = s.processBTCDelegationExpiredEvent(ctx, bbnEvent, blockHeight)
case EventSlashedFinalityProvider:
log.Debug().Msg("Processing slashed finality provider event")
err = s.processSlashedFinalityProviderEvent(ctx, bbnEvent)
err = s.processSlashedFinalityProviderEvent(ctx, bbnEvent, blockHeight)
}

if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion internal/services/expiry_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ func (s *Service) checkExpiry(ctx context.Context) *types.Error {
delegation.StakingTxHashHex,
types.QualifiedStatesForWithdrawable(),
types.StateWithdrawable,
&tlDoc.DelegationSubState,
db.WithSubState(tlDoc.DelegationSubState),
db.WithBtcHeight(int64(tlDoc.ExpireHeight)),
)
if stateUpdateErr != nil {
if db.IsNotFoundError(stateUpdateErr) {
Expand Down
Loading

0 comments on commit e9875a9

Please sign in to comment.