diff --git a/init.sh b/init.sh index e1026075..bbc2a636 100755 --- a/init.sh +++ b/init.sh @@ -34,10 +34,19 @@ cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["gov"]["deposit_para cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["mint"]["params"]["mint_denom"]="aphoton"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json # Custom modules cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["record_rent"]["denom"]="aphoton"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json +cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["authority_rent"]["denom"]="aphoton"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["authority_auction_commit_fee"]["denom"]="aphoton"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["authority_auction_reveal_fee"]["denom"]="aphoton"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["authority_auction_minimum_bid"]["denom"]="aphoton"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json +if [[ "$TEST_EXPIRY" == "true" ]]; then + echo "Setting timers for expiry tests." + + cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["record_rent_duration"]="60s"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json + cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["authority_grace_period"]="60s"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json + cat $HOME/.chibaclonkd/config/genesis.json | jq '.app_state["nameservice"]["params"]["authority_rent_duration"]="60s"' > $HOME/.chibaclonkd/config/tmp_genesis.json && mv $HOME/.chibaclonkd/config/tmp_genesis.json $HOME/.chibaclonkd/config/genesis.json +fi + if [[ "$TEST_AUCTION_ENABLED" == "true" ]]; then echo "Enabling auction and setting timers." diff --git a/x/nameservice/abci.go b/x/nameservice/abci.go index dae9935c..7deb1556 100644 --- a/x/nameservice/abci.go +++ b/x/nameservice/abci.go @@ -13,5 +13,8 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { // EndBlocker Called every block, update validator set func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { + k.ProcessRecordExpiryQueue(ctx) + k.ProcessAuthorityExpiryQueue(ctx) + return []abci.ValidatorUpdate{} } diff --git a/x/nameservice/keeper/keeper.go b/x/nameservice/keeper/keeper.go index 6e16c96a..8004a746 100644 --- a/x/nameservice/keeper/keeper.go +++ b/x/nameservice/keeper/keeper.go @@ -1,6 +1,7 @@ package keeper import ( + "bytes" "fmt" "sort" "time" @@ -302,6 +303,102 @@ func (k Keeper) InsertRecordExpiryQueue(ctx sdk.Context, val types.Record) { k.SetRecordExpiryQueueTimeSlice(ctx, expiryTime, timeSlice) } +// DeleteRecordExpiryQueue deletes a record CID from the record expiry queue. +func (k Keeper) DeleteRecordExpiryQueue(ctx sdk.Context, record types.Record) { + expiryTime, err := time.Parse(time.RFC3339, record.ExpiryTime) + + if err != nil { + panic(err) + } + + timeSlice := k.GetRecordExpiryQueueTimeSlice(ctx, expiryTime) + var newTimeSlice []string + + for _, cid := range timeSlice { + if !bytes.Equal([]byte(cid), []byte(record.Id)) { + newTimeSlice = append(newTimeSlice, cid) + } + } + + if len(newTimeSlice) == 0 { + k.DeleteRecordExpiryQueueTimeSlice(ctx, expiryTime) + } else { + k.SetRecordExpiryQueueTimeSlice(ctx, expiryTime, newTimeSlice) + } +} + +// RecordExpiryQueueIterator returns all the record expiry queue timeslices from time 0 until endTime. +func (k Keeper) RecordExpiryQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + rangeEndBytes := sdk.InclusiveEndBytes(getRecordExpiryQueueTimeKey(endTime)) + return store.Iterator(PrefixExpiryTimeToRecordsIndex, rangeEndBytes) +} + +// GetAllExpiredRecords returns a concatenated list of all the timeslices before currTime. +func (k Keeper) GetAllExpiredRecords(ctx sdk.Context, currTime time.Time) (expiredRecordCIDs []string) { + // Gets an iterator for all timeslices from time 0 until the current block header time. + itr := k.RecordExpiryQueueIterator(ctx, ctx.BlockHeader().Time) + defer itr.Close() + + for ; itr.Valid(); itr.Next() { + timeslice, err := helpers.BytesArrToStringArr(itr.Value()) + + if err != nil { + panic(err) + } + + expiredRecordCIDs = append(expiredRecordCIDs, timeslice...) + } + + return expiredRecordCIDs +} + +// ProcessRecordExpiryQueue tries to renew expiring records (by collecting rent) else marks them as deleted. +func (k Keeper) ProcessRecordExpiryQueue(ctx sdk.Context) { + cids := k.GetAllExpiredRecords(ctx, ctx.BlockHeader().Time) + for _, cid := range cids { + record := k.GetRecord(ctx, cid) + + // If record doesn't have an associated bond or if bond no longer exists, mark it deleted. + if record.BondId == "" || !k.bondKeeper.HasBond(ctx, record.BondId) { + record.Deleted = true + k.PutRecord(ctx, record) + k.DeleteRecordExpiryQueue(ctx, record) + + return + } + + // Try to renew the record by taking rent. + k.TryTakeRecordRent(ctx, record) + } +} + +// TryTakeRecordRent tries to take rent from the record bond. +func (k Keeper) TryTakeRecordRent(ctx sdk.Context, record types.Record) { + params := k.GetParams(ctx) + rent := params.RecordRent + sdkErr := k.bondKeeper.TransferCoinsToModuleAccount(ctx, record.BondId, types.RecordRentModuleAccountName, sdk.NewCoins(rent)) + + if sdkErr != nil { + // Insufficient funds, mark record as deleted. + record.Deleted = true + k.PutRecord(ctx, record) + k.DeleteRecordExpiryQueue(ctx, record) + + return + } + + // Delete old expiry queue entry, create new one. + k.DeleteRecordExpiryQueue(ctx, record) + record.ExpiryTime = ctx.BlockHeader().Time.Add(params.RecordRentDuration).Format(time.RFC3339) + k.InsertRecordExpiryQueue(ctx, record) + + // Save record. + record.Deleted = false + k.PutRecord(ctx, record) + k.AddBondToRecordIndexEntry(ctx, record.BondId, record.Id) +} + // GetModuleBalances gets the nameservice module account(s) balances. func (k Keeper) GetModuleBalances(ctx sdk.Context) []*types.AccountBalance { var balances []*types.AccountBalance diff --git a/x/nameservice/keeper/naming_keeper.go b/x/nameservice/keeper/naming_keeper.go index df710ad4..c5d776b6 100644 --- a/x/nameservice/keeper/naming_keeper.go +++ b/x/nameservice/keeper/naming_keeper.go @@ -1,7 +1,12 @@ package keeper import ( + "bytes" "fmt" + "net/url" + "strings" + "time" + "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -9,9 +14,6 @@ import ( auctiontypes "github.com/tharsis/ethermint/x/auction/types" "github.com/tharsis/ethermint/x/nameservice/helpers" "github.com/tharsis/ethermint/x/nameservice/types" - "net/url" - "strings" - "time" ) func getAuthorityPubKey(pubKey cryptotypes.PubKey) string { @@ -596,6 +598,111 @@ func (k Keeper) SetAuthorityExpiryQueueTimeSlice(ctx sdk.Context, timestamp time store.Set(getAuthorityExpiryQueueTimeKey(timestamp), bz) } +// ProcessAuthorityExpiryQueue tries to renew expiring authorities (by collecting rent) else marks them as expired. +func (k Keeper) ProcessAuthorityExpiryQueue(ctx sdk.Context) { + names := k.GetAllExpiredAuthorities(ctx, ctx.BlockHeader().Time) + for _, name := range names { + authority := k.GetNameAuthority(ctx, name) + + // If authority doesn't have an associated bond or if bond no longer exists, mark it expired. + if authority.BondId == "" || !k.bondKeeper.HasBond(ctx, authority.BondId) { + authority.Status = types.AuthorityExpired + k.SetNameAuthority(ctx, name, &authority) + k.DeleteAuthorityExpiryQueue(ctx, name, authority) + + ctx.Logger().Info(fmt.Sprintf("Marking authority expired as no bond present: %s", name)) + + return + } + + // Try to renew the authority by taking rent. + k.TryTakeAuthorityRent(ctx, name, authority) + } +} + +// DeleteAuthorityExpiryQueueTimeSlice deletes a specific authority expiry queue timeslice. +func (k Keeper) DeleteAuthorityExpiryQueueTimeSlice(ctx sdk.Context, timestamp time.Time) { + store := ctx.KVStore(k.storeKey) + store.Delete(getAuthorityExpiryQueueTimeKey(timestamp)) +} + +// DeleteAuthorityExpiryQueue deletes an authority name from the authority expiry queue. +func (k Keeper) DeleteAuthorityExpiryQueue(ctx sdk.Context, name string, authority types.NameAuthority) { + timeSlice := k.GetAuthorityExpiryQueueTimeSlice(ctx, authority.ExpiryTime) + newTimeSlice := []string{} + + for _, existingName := range timeSlice { + if !bytes.Equal([]byte(existingName), []byte(name)) { + newTimeSlice = append(newTimeSlice, existingName) + } + } + + if len(newTimeSlice) == 0 { + k.DeleteAuthorityExpiryQueueTimeSlice(ctx, authority.ExpiryTime) + } else { + k.SetAuthorityExpiryQueueTimeSlice(ctx, authority.ExpiryTime, newTimeSlice) + } +} + +// GetAllExpiredAuthorities returns a concatenated list of all the timeslices before currTime. +func (k Keeper) GetAllExpiredAuthorities(ctx sdk.Context, currTime time.Time) (expiredAuthorityNames []string) { + // Gets an iterator for all timeslices from time 0 until the current block header time. + itr := k.AuthorityExpiryQueueIterator(ctx, ctx.BlockHeader().Time) + defer itr.Close() + + for ; itr.Valid(); itr.Next() { + timeslice := []string{} + timeslice, err := helpers.BytesArrToStringArr(itr.Value()) + + if err != nil { + panic(err) + } + + expiredAuthorityNames = append(expiredAuthorityNames, timeslice...) + } + + return expiredAuthorityNames +} + +// AuthorityExpiryQueueIterator returns all the authority expiry queue timeslices from time 0 until endTime. +func (k Keeper) AuthorityExpiryQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + rangeEndBytes := sdk.InclusiveEndBytes(getAuthorityExpiryQueueTimeKey(endTime)) + return store.Iterator(PrefixExpiryTimeToAuthoritiesIndex, rangeEndBytes) +} + +// TryTakeAuthorityRent tries to take rent from the authority bond. +func (k Keeper) TryTakeAuthorityRent(ctx sdk.Context, name string, authority types.NameAuthority) { + ctx.Logger().Info(fmt.Sprintf("Trying to take rent for authority: %s", name)) + + params := k.GetParams(ctx) + rent := params.AuthorityRent + sdkErr := k.bondKeeper.TransferCoinsToModuleAccount(ctx, authority.BondId, types.AuthorityRentModuleAccountName, sdk.NewCoins(rent)) + + if sdkErr != nil { + // Insufficient funds, mark authority as expired. + authority.Status = types.AuthorityExpired + k.SetNameAuthority(ctx, name, &authority) + k.DeleteAuthorityExpiryQueue(ctx, name, authority) + + ctx.Logger().Info(fmt.Sprintf("Insufficient funds in owner account to pay authority rent, marking as expired: %s", name)) + + return + } + + // Delete old expiry queue entry, create new one. + k.DeleteAuthorityExpiryQueue(ctx, name, authority) + authority.ExpiryTime = ctx.BlockTime().Add(params.AuthorityRentDuration) + k.InsertAuthorityExpiryQueue(ctx, name, authority.ExpiryTime) + + // Save authority. + authority.Status = types.AuthorityActive + k.SetNameAuthority(ctx, name, &authority) + k.AddBondToAuthorityIndexEntry(ctx, authority.BondId, name) + + ctx.Logger().Info(fmt.Sprintf("Authority rent paid successfully: %s", name)) +} + // ListNameAuthorityRecords - get all name authority records. func (k Keeper) ListNameAuthorityRecords(ctx sdk.Context) map[string]types.NameAuthority { nameAuthorityRecords := make(map[string]types.NameAuthority)