Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
aa5c453
add binary
hard-nett Feb 12, 2025
7e67c69
Merge branch 'bitsongofficial:main' into main
hard-nett Mar 3, 2025
da0ebba
patch: replace cosmos-sdk, bump cometbft
Mar 10, 2025
8c1d0c9
dev: prep for v022 upgrade
Mar 10, 2025
e2aba36
remove binary
Mar 10, 2025
1fe5a81
dev: add custom export cli command
Mar 10, 2025
db258a5
dev: update ed25519 library used in-place-testnet
Mar 10, 2025
54b0f55
bump: add debug mod instructions
Mar 10, 2025
36b5561
logs: update replace docs
Mar 10, 2025
d259750
fix testnet issues
antstalepresh Mar 11, 2025
8a94295
Merge pull request #7 from antstalepresh/fix/testnet
hard-nett Mar 11, 2025
8817d90
docs
Mar 11, 2025
cff2de4
readd v021 upgradeHandler for in-place-testnet
Mar 11, 2025
4ec330a
add historical custom upgrade patch logic
Mar 11, 2025
c72ca38
dev: remove unnecessary x/account feegrant query
Mar 11, 2025
bbd8a08
dev: bumps to upggradeHandler logs & tests
Mar 11, 2025
dc38640
dev: bump export custom
Mar 11, 2025
64d02b0
dev: return err instead of panic, require no error in tests
Mar 11, 2025
8a3bce1
add default replace, bump docker alpine version
Mar 12, 2025
34ea59c
go mod tidy
Mar 12, 2025
a14dc3d
wip: retain broken validator in testnet for patch assertion
Mar 20, 2025
73d635b
wip: retain validator in staking store
Mar 21, 2025
5d819f8
tests: 1 log file printed for debugging v022 upgradeHandler, added up…
Mar 21, 2025
e1b1b11
Merge pull request #8 from permissionlessweb/v022-custom-testnet
hard-nett Mar 21, 2025
290c4d3
dev: access appKeepers from top level app
Mar 21, 2025
9460fe8
Merge pull request #9 from permissionlessweb/keeper-bump
hard-nett Mar 21, 2025
263a92c
test: add unbonded control validator to unit tests
Apr 12, 2025
6476cfd
spellcheck
May 10, 2025
1f6418c
cosmos-sdk@v0.53, implement x/protocol-pool
May 10, 2025
c5672c6
register protocol pool in our app modules basic manager
May 10, 2025
15a441c
4444
May 10, 2025
7ee279f
ict: impl x/protocolpool tests
May 10, 2025
3e70aba
protocol-pool test
May 10, 2025
1dc9872
uncomment tests
May 10, 2025
9232ccd
init protocolpool keeper prior to distribution keeper
May 10, 2025
1561af6
Merge branch 'bitsongofficial:main' into v0.22.0-rc
hard-nett May 10, 2025
424ccd2
saftey
May 10, 2025
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
24 changes: 9 additions & 15 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (
capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types"
wasmlctypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"

"cosmossdk.io/api/cosmos/crypto/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/types/bech32"

reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
Expand All @@ -61,7 +61,7 @@ import (
"github.com/cometbft/cometbft/libs/bytes"
tmjson "github.com/cometbft/cometbft/libs/json"
tmos "github.com/cometbft/cometbft/libs/os"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"

"github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
Expand All @@ -70,15 +70,9 @@ import (

"github.com/bitsongofficial/go-bitsong/app/keepers"
"github.com/bitsongofficial/go-bitsong/app/upgrades"
v010 "github.com/bitsongofficial/go-bitsong/app/upgrades/v010"
v011 "github.com/bitsongofficial/go-bitsong/app/upgrades/v011"
v013 "github.com/bitsongofficial/go-bitsong/app/upgrades/v013"
v014 "github.com/bitsongofficial/go-bitsong/app/upgrades/v014"
v015 "github.com/bitsongofficial/go-bitsong/app/upgrades/v015"
v016 "github.com/bitsongofficial/go-bitsong/app/upgrades/v016"
v018 "github.com/bitsongofficial/go-bitsong/app/upgrades/v018"
v020 "github.com/bitsongofficial/go-bitsong/app/upgrades/v020"

v021 "github.com/bitsongofficial/go-bitsong/app/upgrades/v021"
v022 "github.com/bitsongofficial/go-bitsong/app/upgrades/v022"
// unnamed import of statik for swagger UI support
// _ "github.com/bitsongofficial/go-bitsong/swagger/statik"
)
Expand All @@ -99,9 +93,9 @@ var (
EnableSpecificProposals = ""

Upgrades = []upgrades.Upgrade{
v010.Upgrade, v011.Upgrade, v013.Upgrade, v014.Upgrade,
v015.Upgrade, v016.Upgrade, v018.Upgrade, v020.Upgrade,
v021.Upgrade,
// v010.Upgrade, v011.Upgrade, v013.Upgrade, v014.Upgrade,
// v015.Upgrade, v016.Upgrade, v018.Upgrade, v020.Upgrade,
v021.Upgrade, v022.Upgrade,
}
)

Expand Down Expand Up @@ -397,7 +391,7 @@ func NewBitsongApp(
if err := app.LoadLatestVersion(); err != nil {
tmos.Exit(err.Error())
}
ctx := app.BaseApp.NewUncachedContext(true, tmproto.Header{})
ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{})
// Initialize pinned codes in wasmvm as they are not persisted there
if err := app.AppKeepers.WasmKeeper.InitializePinnedCodes(ctx); err != nil {
tmos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err))
Expand Down Expand Up @@ -660,7 +654,7 @@ func getReflectionService() *runtimeservices.ReflectionService {
// source: https://github.com/osmosis-labs/osmosis/blob/7b1a78d397b632247fe83f51867f319adf3a858c/app/app.go#L786
func InitBitsongAppForTestnet(app *BitsongApp, newValAddr bytes.HexBytes, newValPubKey crypto.PubKey, newOperatorAddress, upgradeToTrigger string) *BitsongApp {

ctx := app.BaseApp.NewUncachedContext(true, tmproto.Header{})
ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{})
pubkey := &ed25519.PubKey{Key: newValPubKey.Bytes()}
pubkeyAny, err := types.NewAnyWithValue(pubkey)
if err != nil {
Expand Down
298 changes: 298 additions & 0 deletions app/export_custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
package app

// to run: bitsongd custom-export --output-document v0.21.5-export.json
// defaults to for zero height
import (
"encoding/json"
"fmt"
"log"

storetypes "cosmossdk.io/store/types"
v022 "github.com/bitsongofficial/go-bitsong/app/upgrades/v022"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"

distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// - validator slash event:
// - historical reference:
//
// x/slashing:
// - validator signing info:
// - historical reference:
func (app *BitsongApp) CustomExportAppStateAndValidators(

forZeroHeight bool, jailAllowedAddrs []string,

) (servertypes.ExportedApp, error) {
// as if they could withdraw from the start of the next block
ctx := app.NewContext(true)

// We export at last height + 1, because that's the height at which
// Tendermint will start InitChain.
height := app.LastBlockHeight() + 1
if forZeroHeight {
height = 0
app.customTestUpgradeHandlerLogicViaExport(ctx, jailAllowedAddrs)
}

genState, _ := app.mm.ExportGenesis(ctx, app.appCodec)
appState, err := json.MarshalIndent(genState, "", " ")
if err != nil {
return servertypes.ExportedApp{}, err
}

validators, err := staking.WriteValidators(ctx, app.AppKeepers.StakingKeeper)
if err != nil {
return servertypes.ExportedApp{}, err
}
return servertypes.ExportedApp{
AppState: appState,
Validators: validators,
Height: height,
ConsensusParams: app.BaseApp.GetConsensusParams(ctx),
}, nil
}

// prepare for fresh start at zero height
// NOTE zero height genesis is a temporary feature which will be deprecated
//
// in favour of export at a block height
func (app *BitsongApp) customTestUpgradeHandlerLogicViaExport(ctx sdk.Context, jailAllowedAddrs []string) {
applyAllowedAddrs := false

condJSON := v022.ConditionalJSON{
PatchDelegationCount: 0,
ZeroSharesDelegation: make([]v022.ZeroSharesDelegation, 0),
PatchedDelegation: make([]v022.PatchedDelegation, 0),
DistSlashStore: v022.DistrSlashObject{},
}

// check if there is a allowed address list
if len(jailAllowedAddrs) > 0 {
applyAllowedAddrs = true
}

allowedAddrsMap := make(map[string]bool)
for _, addr := range jailAllowedAddrs {
_, err := sdk.ValAddressFromBech32(addr)
if err != nil {
log.Fatal(err)
}
allowedAddrsMap[addr] = true

}
// // debug module
err := v022.CustomV022PatchLogic(ctx, &app.AppKeepers, true)
if err != nil {
panic(err)
}

Comment on lines +78 to +83

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Panic / log.Fatal combo yields inconsistent termination semantics
This function mixes log.Fatal (which os.Exits — no defer runs) and panic (which unwinds the stack). Choose one strategy or, better, propagate errors to the caller so the CLI can decide what to do.

Suggestion: turn the function into customTestUpgradeHandlerLogicViaExport(...) error and bubble errors up through CustomExportAppStateAndValidators, keeping the CLI responsible for exiting.

🤖 Prompt for AI Agents (early access)
In app/export_custom.go around lines 78 to 83, the code currently panics on
error, mixing panic and log.Fatal which causes inconsistent termination
behavior. Refactor the function to return an error instead of panicking, rename
it to customTestUpgradeHandlerLogicViaExport(...) error, and propagate the error
up through CustomExportAppStateAndValidators so the CLI caller can handle the
error and decide whether to exit or not.

// simapp export for 0 height state logic:
// withdraw all validator commission
err = app.AppKeepers.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
valBz, err := app.AppKeepers.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
panic(err)
}
_, _ = app.AppKeepers.DistrKeeper.WithdrawValidatorCommission(ctx, valBz)
return false
})
if err != nil {
panic(err)
}

// count slashes for all current validators
slashCount := uint64(0)
app.AppKeepers.DistrKeeper.IterateValidatorSlashEvents(ctx,
func(val sdk.ValAddress, _ uint64, vse distrtypes.ValidatorSlashEvent) (stop bool) {
var existing *v022.DistrSlashEvent
for i, obj := range condJSON.DistSlashStore.DistrSlashEvent {
if obj.Val == val.String() {
existing = &condJSON.DistSlashStore.DistrSlashEvent[i]
break
}
} // Create a new DistrSlashEvent from the current event
event := v022.Slash{
Fraction: vse.Fraction.String(),
Period: vse.ValidatorPeriod,
}

// If existing object found, append the event and increment count
if existing != nil {
existing.Slashes = append(existing.Slashes, event)
existing.SlashEventCount++
} else {
// Create a new DistrSlashObject
newSlashObj := v022.DistrSlashEvent{
Val: val.String(),
SlashEventCount: 1,
Slashes: []v022.Slash{event},
}
condJSON.DistSlashStore.DistrSlashEvent = append(condJSON.DistSlashStore.DistrSlashEvent, newSlashObj)
}
slashCount++
return false
})
condJSON.DistSlashStore.SlashEventCount = slashCount

// /* Just to be safe, assert the invariants on current state. */
app.AppKeepers.CrisisKeeper.AssertInvariants(ctx)

app.AppKeepers.StakingKeeper.IterateAllDelegations(ctx, func(del stakingtypes.Delegation) (stop bool) {
valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
if err != nil {
panic(err)
}
// delAddr := sdk.AccAddress(del.DelegatorAddress)
val, err := app.AppKeepers.StakingKeeper.GetValidator(ctx, valAddr)
if err != nil {
panic(err)
}
endingPeriod, err := app.AppKeepers.DistrKeeper.IncrementValidatorPeriod(ctx, val)
if err != nil {
panic(err)
}
_, err = app.AppKeepers.DistrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
if err != nil {
panic(err)
}

return false

})

// withdraw all delegator rewards
dels, err := app.AppKeepers.StakingKeeper.GetAllDelegations(ctx)
if err != nil {
panic(err)
}

// clear validator slash events
Comment on lines +124 to +130

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Delegator rewards are NOT withdrawn, comment is misleading and rewards are burned
The comment says “withdraw all delegator rewards”, but the code only fetches delegations. Historical rewards are deleted right after, so outstanding rewards will become unclaimable.

 dls, err := app.StakingKeeper.GetAllDelegations(ctx)
 if err != nil {
     panic(err)
 }
+for _, del := range dls {
+    delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress)
+    valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
+    if err != nil {
+        panic(err)
+    }
+    if _, err := app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr); err != nil {
+        panic(err)
+    }
+}

Without this, user funds are effectively lost after an export-at-zero-height.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// withdraw all delegator rewards
dels, err := app.StakingKeeper.GetAllDelegations(ctx)
if err != nil {
panic(err)
}
// clear validator slash events
// withdraw all delegator rewards
dels, err := app.StakingKeeper.GetAllDelegations(ctx)
if err != nil {
panic(err)
}
for _, del := range dels {
delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress)
valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
if err != nil {
panic(err)
}
if _, err := app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr); err != nil {
panic(err)
}
}
// clear validator slash events
🤖 Prompt for AI Agents (early access)
In app/export_custom.go around lines 124 to 130, the comment incorrectly states
that delegator rewards are withdrawn, but the code only fetches delegations and
then deletes historical rewards, causing rewards to be burned and unclaimable.
Update the comment to accurately reflect that rewards are not withdrawn but
effectively removed, and if the intended behavior is to preserve rewards, modify
the code to properly withdraw or transfer delegator rewards before clearing
historical data.

app.AppKeepers.DistrKeeper.DeleteAllValidatorSlashEvents(ctx)

// clear validator historical rewards
app.AppKeepers.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx)

// reinitialize all validators
err = app.AppKeepers.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
valBz, err := app.AppKeepers.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
panic(err)
}
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
scraps, err := app.AppKeepers.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz)
if err != nil {
panic(err)
}
feePool, err := app.AppKeepers.DistrKeeper.FeePool.Get(ctx)
if err != nil {
panic(err)
}
feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
if err := app.AppKeepers.DistrKeeper.FeePool.Set(ctx, feePool); err != nil {
panic(err)
}

if err := app.AppKeepers.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil {
panic(err)
}
return false
})
if err != nil {
panic(err)
}

// reinitialize all delegations
for _, del := range dels {
valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
if err != nil {
panic(err)
}
delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress)

if err := app.AppKeepers.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); err != nil {
// never called as BeforeDelegationCreated always returns nil
panic(fmt.Errorf("error while incrementing period: %w", err))
}

if err := app.AppKeepers.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); err != nil {
// never called as AfterDelegationModified always returns nil
panic(fmt.Errorf("error while creating a new delegation period record: %w", err))
}
}
// iterate through redelegations, reset creation height
app.AppKeepers.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) {
for i := range red.Entries {
red.Entries[i].CreationHeight = 0
}
err = app.AppKeepers.StakingKeeper.SetRedelegation(ctx, red)
if err != nil {
panic(err)
}
return false
})
// iterate through unbonding delegations, reset creation height
app.AppKeepers.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) {
for i := range ubd.Entries {
ubd.Entries[i].CreationHeight = 0
}
err = app.AppKeepers.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
if err != nil {
panic(err)
}
return false
})

// Iterate through validators by power descending, reset bond heights, and
// update bond intra-tx counters.
store := ctx.KVStore(app.GetKey(stakingtypes.StoreKey))
iter := storetypes.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey)
counter := int16(0)

for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key()))
validator, err := app.AppKeepers.StakingKeeper.GetValidator(ctx, addr)
if err != nil {
panic("expected validator, not found")
}

validator.UnbondingHeight = 0
if applyAllowedAddrs && !allowedAddrsMap[addr.String()] {
validator.Jailed = true
}

app.AppKeepers.StakingKeeper.SetValidator(ctx, validator)
counter++
}

if err := iter.Close(); err != nil {
app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", err)
return
}

_, err = app.AppKeepers.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
if err != nil {
log.Fatal(err)
}

/* Handle slashing state. */

// reset start height on signing infos
app.AppKeepers.SlashingKeeper.IterateValidatorSigningInfos(
ctx,
func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) {
info.StartHeight = 0
app.AppKeepers.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info)
return false
},
)

// Marshal the ConditionalJSON object to JSON
v022.PrintConditionalJsonLogs(condJSON, "conditional.json")

}
Comment thread
hard-nett marked this conversation as resolved.
23 changes: 0 additions & 23 deletions app/upgrades/v010/constants.go

This file was deleted.

Loading