Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: store state history in db #113

Merged
merged 5 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading