forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Problem: no efficient way to collect fee
Solution: - support an idea of virtual account in bank module, where the incoming coins are accumulated in a per-tx object store first, then accumulate and credit to the real account at end blocker. it's nesserary to support parallel tx execution, where we try not to access shared states.
- Loading branch information
Showing
10 changed files
with
206 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package keeper | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/binary" | ||
"encoding/hex" | ||
"fmt" | ||
|
||
errorsmod "cosmossdk.io/errors" | ||
"github.com/cosmos/cosmos-sdk/telemetry" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
"github.com/cosmos/cosmos-sdk/x/bank/types" | ||
) | ||
|
||
func (k BaseSendKeeper) SendCoinsFromAccountToModuleVirtual( | ||
ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins, | ||
) error { | ||
recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule) | ||
if recipientAcc == nil { | ||
return errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule) | ||
} | ||
|
||
return k.SendCoinsVirtual(ctx, senderAddr, recipientAcc.GetAddress(), amt) | ||
} | ||
|
||
// SendCoinsVirtual accumulate the recipient's coins in a per-transaction transient state, | ||
// which are sumed up and added to the real account at the end of block. | ||
// Events are emiited the same as normal send. | ||
func (k BaseSendKeeper) SendCoinsVirtual(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error { | ||
var err error | ||
err = k.subUnlockedCoins(ctx, fromAddr, amt) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
toAddr, err = k.sendRestriction.apply(ctx, fromAddr, toAddr, amt) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
k.addVirtualCoins(ctx, toAddr, amt) | ||
|
||
// bech32 encoding is expensive! Only do it once for fromAddr | ||
fromAddrString := fromAddr.String() | ||
sdkCtx := sdk.UnwrapSDKContext(ctx) | ||
sdkCtx.EventManager().EmitEvents(sdk.Events{ | ||
sdk.NewEvent( | ||
types.EventTypeTransfer, | ||
sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()), | ||
sdk.NewAttribute(types.AttributeKeySender, fromAddrString), | ||
sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()), | ||
), | ||
sdk.NewEvent( | ||
sdk.EventTypeMessage, | ||
sdk.NewAttribute(types.AttributeKeySender, fromAddrString), | ||
), | ||
}) | ||
|
||
return nil | ||
} | ||
|
||
func (k BaseSendKeeper) addVirtualCoins(ctx context.Context, addr sdk.AccAddress, amt sdk.Coins) { | ||
sdkCtx := sdk.UnwrapSDKContext(ctx) | ||
store := sdkCtx.ObjectStore(k.objStoreKey) | ||
|
||
key := make([]byte, len(addr)+8) | ||
copy(key, addr) | ||
binary.BigEndian.PutUint64(key[len(addr):], uint64(sdkCtx.TxIndex())) | ||
|
||
var coins sdk.Coins | ||
value := store.Get(key) | ||
if value != nil { | ||
coins = value.(sdk.Coins) | ||
} | ||
coins = coins.Add(amt...) | ||
store.Set(key, coins) | ||
} | ||
|
||
// CreditVirtualAccounts sum up the transient coins and add them to the real account, | ||
// should be called at end blocker. | ||
func (k BaseSendKeeper) CreditVirtualAccounts(ctx context.Context) error { | ||
store := sdk.UnwrapSDKContext(ctx).ObjectStore(k.objStoreKey) | ||
|
||
var toAddr sdk.AccAddress | ||
var sum sdk.Coins | ||
flushCurrentAddr := func() { | ||
if sum.IsZero() { | ||
return | ||
} | ||
|
||
k.addCoins(ctx, toAddr, sum) | ||
Check warning Code scanning / gosec Errors unhandled. Warning
Errors unhandled.
|
||
sum = sum[:0] | ||
|
||
// Create account if recipient does not exist. | ||
// | ||
// NOTE: This should ultimately be removed in favor a more flexible approach | ||
// such as delegated fee messages. | ||
accExists := k.ak.HasAccount(ctx, toAddr) | ||
if !accExists { | ||
defer telemetry.IncrCounter(1, "new", "account") | ||
k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr)) | ||
} | ||
} | ||
|
||
it := store.Iterator(nil, nil) | ||
defer it.Close() | ||
for ; it.Valid(); it.Next() { | ||
if len(it.Key()) <= 8 { | ||
return fmt.Errorf("unexpected key length: %s", hex.EncodeToString(it.Key())) | ||
} | ||
|
||
addr := it.Key()[:len(it.Key())-8] | ||
if !bytes.Equal(toAddr, addr) { | ||
flushCurrentAddr() | ||
toAddr = addr | ||
} | ||
|
||
coins := it.Value().(sdk.Coins) | ||
// TODO more efficient coins sum | ||
sum = sum.Add(coins...) | ||
} | ||
|
||
flushCurrentAddr() | ||
return nil | ||
} |
Oops, something went wrong.