diff --git a/x/btclightclient/keeper/keeper.go b/x/btclightclient/keeper/keeper.go index ba013434..9160bcc2 100644 --- a/x/btclightclient/keeper/keeper.go +++ b/x/btclightclient/keeper/keeper.go @@ -21,6 +21,8 @@ import ( type ( Keeper struct { + BaseUTXOKeeper + cdc codec.BinaryCodec storeKey storetypes.StoreKey memKey storetypes.StoreKey @@ -37,10 +39,11 @@ func NewKeeper( bankKeeper types.BankKeeper, ) *Keeper { return &Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, - bankKeeper: bankKeeper, + cdc: cdc, + storeKey: storeKey, + memKey: memKey, + bankKeeper: bankKeeper, + BaseUTXOKeeper: *NewBaseUTXOKeeper(cdc, storeKey), } } @@ -272,8 +275,8 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg IsLocked: false, } - k.SetUtxo(ctx, utxo) - k.SetOwnerUtxo(ctx, utxo) + k.SetUTXO(ctx, &utxo) + k.SetOwnerUTXO(ctx, &utxo) ctx.Logger().Info("Minted Bitcoin Voucher", "index", i, "address", addr.EncodeAddress(), "amount", out.Value, "sender", sender.EncodeAddress(), "senderAddr", senderAddr.String(), "coins", coins.String()) @@ -284,17 +287,6 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg return nil } -func (k Keeper) SetUtxo(ctx sdk.Context, utxo types.UTXO) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshal(&utxo) - store.Set(types.BtcUtxoKey(utxo.Txid, utxo.Vout), bz) -} - -func (k Keeper) SetOwnerUtxo(ctx sdk.Context, utxo types.UTXO) { - store := ctx.KVStore(k.storeKey) - store.Set(types.BtcOwnerUtxoKey(utxo.Address, utxo.Txid, utxo.Vout), nil) -} - func (k Keeper) GetBlockHeader(ctx sdk.Context, hash string) *types.BlockHeader { store := ctx.KVStore(k.storeKey) var blockHeader types.BlockHeader diff --git a/x/btclightclient/keeper/utxo.go b/x/btclightclient/keeper/utxo.go index 2423e3df..9c4110dc 100644 --- a/x/btclightclient/keeper/utxo.go +++ b/x/btclightclient/keeper/utxo.go @@ -1,6 +1,11 @@ package keeper import ( + "math/big" + "sort" + + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sideprotocol/side/x/btclightclient/types" @@ -8,70 +13,265 @@ import ( type UTXOViewKeeper interface { HasUTXO(ctx sdk.Context, hash string, vout uint64) bool - IsUTXOLocked(ctx sdk.Context, hash string, vout uint64) bool + IsUTXOLocked(ctx sdk.Context, hash string, vout uint64) (bool, error) + GetUTXO(ctx sdk.Context, hash string, vout uint64) *types.UTXO GetAllUTXOs(ctx sdk.Context) []*types.UTXO GetUTXOsByAddr(ctx sdk.Context, addr string) []*types.UTXO + GetUnlockedUTXOsByAddr(ctx sdk.Context, addr string) []*types.UTXO GetOrderedUTXOsByAddr(ctx sdk.Context, addr string) []*types.UTXO + + IterateAllUTXOs(ctx sdk.Context, cb func(utxo *types.UTXO) (stop bool)) + IterateUTXOsByAddr(ctx sdk.Context, addr string, cb func(addr string, utxo *types.UTXO) (stop bool)) } type UTXOKeeper interface { UTXOViewKeeper - LockUTXO(ctx sdk.Context, hash string, vout uint64) - SpendUTXO(ctx sdk.Context, hash string, vout uint64) + LockUTXO(ctx sdk.Context, hash string, vout uint64) error + LockUTXOs(ctx sdk.Context, utxos []*types.UTXO) error + + UnlockUTXO(ctx sdk.Context, hash string, vout uint64) error + UnlockUTXOs(ctx sdk.Context, utxos []*types.UTXO) error + + SpendUTXO(ctx sdk.Context, hash string, vout uint64) error + SpendUTXOs(ctx sdk.Context, utxos []*types.UTXO) error } var _ UTXOKeeper = (*BaseUTXOKeeper)(nil) type BaseUTXOViewKeeper struct { - k Keeper + cdc codec.BinaryCodec + storeKey storetypes.StoreKey } -func NewBaseUTXOViewKeeper(k Keeper) *BaseUTXOViewKeeper { - return &BaseUTXOViewKeeper{k} +func NewBaseUTXOViewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey) *BaseUTXOViewKeeper { + return &BaseUTXOViewKeeper{ + cdc, + storeKey, + } } func (bvk *BaseUTXOViewKeeper) HasUTXO(ctx sdk.Context, hash string, vout uint64) bool { - // TODO - return true + store := ctx.KVStore(bvk.storeKey) + return store.Has(types.BtcUtxoKey(hash, vout)) } -func (bvk *BaseUTXOViewKeeper) IsUTXOLocked(ctx sdk.Context, hash string, vout uint64) bool { - // TODO - return true +func (bvk *BaseUTXOViewKeeper) IsUTXOLocked(ctx sdk.Context, hash string, vout uint64) (bool, error) { + if !bvk.HasUTXO(ctx, hash, vout) { + return false, types.ErrUTXODoesNotExist + } + + utxo := bvk.GetUTXO(ctx, hash, vout) + + return utxo.IsLocked, nil +} + +func (bvk *BaseUTXOViewKeeper) GetUTXO(ctx sdk.Context, hash string, vout uint64) *types.UTXO { + store := ctx.KVStore(bvk.storeKey) + + var utxo types.UTXO + bz := store.Get(types.BtcUtxoKey(hash, vout)) + bvk.cdc.MustUnmarshal(bz, &utxo) + + return &utxo } func (bvk *BaseUTXOViewKeeper) GetAllUTXOs(ctx sdk.Context) []*types.UTXO { - // TODO - return nil + utxos := make([]*types.UTXO, 0) + + bvk.IterateAllUTXOs(ctx, func(utxo *types.UTXO) (stop bool) { + utxos = append(utxos, utxo) + return false + }) + + return utxos } func (bvk *BaseUTXOViewKeeper) GetUTXOsByAddr(ctx sdk.Context, addr string) []*types.UTXO { - // TODO - return nil + utxos := make([]*types.UTXO, 0) + + bvk.IterateUTXOsByAddr(ctx, addr, func(addr string, utxo *types.UTXO) (stop bool) { + utxos = append(utxos, utxo) + return false + }) + + return utxos +} + +func (bvk *BaseUTXOViewKeeper) GetUnlockedUTXOsByAddr(ctx sdk.Context, addr string) []*types.UTXO { + utxos := make([]*types.UTXO, 0) + + bvk.IterateUTXOsByAddr(ctx, addr, func(addr string, utxo *types.UTXO) (stop bool) { + if !utxo.IsLocked { + utxos = append(utxos, utxo) + } + + return false + }) + + return utxos } +// GetOrderedUTXOsByAddr gets all unlocked utxos of the given address in the descending order by amount func (bvk *BaseUTXOViewKeeper) GetOrderedUTXOsByAddr(ctx sdk.Context, addr string) []*types.UTXO { - // TODO - return nil + utxos := make([]*types.UTXO, 0) + + bvk.IterateUTXOsByAddr(ctx, addr, func(addr string, utxo *types.UTXO) (stop bool) { + if !utxo.IsLocked { + utxos = append(utxos, utxo) + } + + return false + }) + + // sort utoxs in the descending order + sort.SliceStable(utxos, func(i int, j int) bool { + return utxos[i].Amount > utxos[j].Amount + }) + + return utxos +} + +func (bvk *BaseUTXOViewKeeper) IterateAllUTXOs(ctx sdk.Context, cb func(utxo *types.UTXO) (stop bool)) { + store := ctx.KVStore(bvk.storeKey) + + iterator := sdk.KVStorePrefixIterator(store, types.BtcUtxoKeyPrefix) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var utxo types.UTXO + bvk.cdc.MustUnmarshal(iterator.Value(), &utxo) + + if cb(&utxo) { + break + } + } +} + +func (bvk *BaseUTXOViewKeeper) IterateUTXOsByAddr(ctx sdk.Context, addr string, cb func(addr string, utxo *types.UTXO) (stop bool)) { + store := ctx.KVStore(bvk.storeKey) + + keyPrefix := append(types.BtcOwnerUtxoKeyPrefix, []byte(addr)...) + iterator := sdk.KVStorePrefixIterator(store, keyPrefix) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + key := iterator.Key() + + hash := key[1+len(addr) : 1+len(addr)+64] + vout := key[1+len(addr)+64:] + + utxo := bvk.GetUTXO(ctx, string(hash), new(big.Int).SetBytes(vout).Uint64()) + if cb(addr, utxo) { + break + } + } } type BaseUTXOKeeper struct { BaseUTXOViewKeeper - k Keeper + cdc codec.BinaryCodec + storeKey storetypes.StoreKey +} + +func NewBaseUTXOKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey) *BaseUTXOKeeper { + return &BaseUTXOKeeper{ + BaseUTXOViewKeeper: *NewBaseUTXOViewKeeper(cdc, storeKey), + cdc: cdc, + storeKey: storeKey, + } +} + +func (bk *BaseUTXOKeeper) SetUTXO(ctx sdk.Context, utxo *types.UTXO) { + store := ctx.KVStore(bk.storeKey) + + bz := bk.cdc.MustMarshal(utxo) + store.Set(types.BtcUtxoKey(utxo.Txid, utxo.Vout), bz) +} + +func (bk *BaseUTXOKeeper) SetOwnerUTXO(ctx sdk.Context, utxo *types.UTXO) { + store := ctx.KVStore(bk.storeKey) + + store.Set(types.BtcOwnerUtxoKey(utxo.Address, utxo.Txid, utxo.Vout), nil) } -func NewBaseUTXOKeeper(k Keeper) *BaseUTXOKeeper { - return &BaseUTXOKeeper{BaseUTXOViewKeeper: *NewBaseUTXOViewKeeper(k), k: k} +func (bk *BaseUTXOKeeper) LockUTXO(ctx sdk.Context, hash string, vout uint64) error { + if !bk.HasUTXO(ctx, hash, vout) { + return types.ErrUTXODoesNotExist + } + + utxo := bk.GetUTXO(ctx, hash, vout) + if utxo.IsLocked { + return types.ErrUTXOLocked + } + + utxo.IsLocked = true + bk.SetUTXO(ctx, utxo) + + return nil } -func (bk *BaseUTXOKeeper) LockUTXO(ctx sdk.Context, hash string, vout uint64) { - // TODO +func (bk *BaseUTXOKeeper) LockUTXOs(ctx sdk.Context, utxos []*types.UTXO) error { + for _, utxo := range utxos { + if err := bk.LockUTXO(ctx, utxo.Txid, utxo.Vout); err != nil { + return err + } + } + + return nil } -func (bk *BaseUTXOKeeper) SpendUTXO(ctx sdk.Context, hash string, vout uint64) { - // TODO +func (bk *BaseUTXOKeeper) UnlockUTXO(ctx sdk.Context, hash string, vout uint64) error { + if !bk.HasUTXO(ctx, hash, vout) { + return types.ErrUTXODoesNotExist + } + + utxo := bk.GetUTXO(ctx, hash, vout) + if !utxo.IsLocked { + return types.ErrUTXOUnlocked + } + + utxo.IsLocked = false + bk.SetUTXO(ctx, utxo) + + return nil +} + +func (bk *BaseUTXOKeeper) UnlockUTXOs(ctx sdk.Context, utxos []*types.UTXO) error { + for _, utxo := range utxos { + if err := bk.UnlockUTXO(ctx, utxo.Txid, utxo.Vout); err != nil { + return err + } + } + + return nil +} + +func (bk *BaseUTXOKeeper) SpendUTXO(ctx sdk.Context, hash string, vout uint64) error { + if !bk.HasUTXO(ctx, hash, vout) { + return types.ErrUTXODoesNotExist + } + + bk.removeUTXO(ctx, hash, vout) + + return nil +} + +func (bk *BaseUTXOKeeper) SpendUTXOs(ctx sdk.Context, utxos []*types.UTXO) error { + for _, utxo := range utxos { + if err := bk.SpendUTXO(ctx, utxo.Txid, utxo.Vout); err != nil { + return err + } + } + + return nil +} + +func (bk *BaseUTXOKeeper) removeUTXO(ctx sdk.Context, hash string, vout uint64) { + store := ctx.KVStore(bk.storeKey) + + store.Delete(types.BtcUtxoKey(hash, vout)) } diff --git a/x/btclightclient/types/errors.go b/x/btclightclient/types/errors.go index b55b4573..41bee256 100644 --- a/x/btclightclient/types/errors.go +++ b/x/btclightclient/types/errors.go @@ -24,4 +24,8 @@ var ( ErrInvalidSignatures = errorsmod.Register(ModuleName, 4200, "invalid signatures") ErrInsufficientBalance = errorsmod.Register(ModuleName, 4201, "insufficient balance") + + ErrUTXODoesNotExist = errorsmod.Register(ModuleName, 5100, "utxo does not exist") + ErrUTXOLocked = errorsmod.Register(ModuleName, 5101, "utxo locked") + ErrUTXOUnlocked = errorsmod.Register(ModuleName, 5102, "utxo unlocked") ) diff --git a/x/btclightclient/types/keys.go b/x/btclightclient/types/keys.go index 82702e5e..3e66d3fd 100644 --- a/x/btclightclient/types/keys.go +++ b/x/btclightclient/types/keys.go @@ -22,10 +22,6 @@ func KeyPrefix(p string) []byte { } var ( - // key separator - // only used when the separated keys do not contain the separator - KeySeparator = []byte{0x0} - ParamsStoreKey = []byte{0x1} SequenceKey = []byte{0x2} @@ -53,7 +49,6 @@ func BtcUtxoKey(hash string, vout uint64) []byte { func BtcOwnerUtxoKey(owner string, hash string, vout uint64) []byte { key := append(BtcOwnerUtxoKeyPrefix, []byte(owner)...) - key = append(key, KeySeparator...) key = append(key, []byte(hash)...) return append(key, Int64ToBytes(vout)...)