Commit 69dfe9fa authored by Son of Odin's avatar Son of Odin 💬

Merge branch 'slash-ygg-patch' into 'master'

[bugfix] slash yggdrasil when funds are stolen with a bad memo

See merge request !692
parents 96f8ba44 1c159d00
Pipeline #129766626 passed with stages
in 62 minutes and 44 seconds
......@@ -506,7 +506,7 @@ func (b *Binance) BroadcastTx(tx stypes.TxOutItem, hexTx []byte) error {
var commit stypes.Commit
err = json.Unmarshal(body, &commit)
if err != nil || len(commit.Logs) == 0 {
b.logger.Error().Err(err).Msg("fail unmarshal commit")
b.logger.Error().Err(err).Msgf("fail unmarshal commit: %s", string(body))
var badCommit stypes.BadCommit // since commit doesn't work, lets try bad commit
err = json.Unmarshal(body, &badCommit)
......
......@@ -117,6 +117,31 @@ func (h ObservedTxOutHandler) handleV1(ctx sdk.Context, msg MsgObservedTxOut) sd
continue
}
// if memo isn't valid and its funds moving from a yggdrasil vault,
// slash the node
_, err = ParseMemo(tx.Tx.Memo)
if err != nil {
vault, err := h.keeper.GetVault(ctx, tx.ObservedPubKey)
if err != nil {
ctx.Logger().Error("fail to get vault", "error", err)
continue
}
if vault.IsYggdrasil() {
// a yggdrasil vault has apparently stolen funds, slash them
for _, c := range append(tx.Tx.Coins, tx.Tx.Gas.ToCoins()...) {
if err := slashNodeAccount(ctx, h.keeper, tx.ObservedPubKey, c.Asset, c.Amount); err != nil {
ctx.Logger().Error("fail to slash account for sending extra fund", "error", err)
}
}
vault.SubFunds(tx.Tx.Coins)
vault.SubFunds(tx.Tx.Gas.ToCoins())
if err := h.keeper.SetVault(ctx, vault); err != nil {
ctx.Logger().Error("fail to save vault", "error", err)
}
continue
}
}
txOut := voter.GetTx(activeNodeAccounts) // get consensus tx, in case our for loop is incorrect
m, err := processOneTxIn(ctx, h.keeper, txOut, msg.Signer)
if err != nil || tx.Tx.Chain.IsEmpty() {
......@@ -125,12 +150,8 @@ func (h ObservedTxOutHandler) handleV1(ctx sdk.Context, msg MsgObservedTxOut) sd
"tx", tx.Tx.String())
continue
}
// when thorchain fail to parse the out going tx memo, likely it is an
// unauthorised tx in that case, thorchain doesn't subtract the fund
// from relevant vault, thus when the node/yggdrasil leave, they will
// either return those asset or they will be slashed for that amount,
// also if the tx memo is unknown , thorchain also doesn't subsidise
// gas Apply Gas fees
// Apply Gas fees
if err := AddGasFees(ctx, h.keeper, tx); err != nil {
return sdk.ErrInternal(fmt.Errorf("fail to add gas fee: %w", err).Error()).Result()
}
......@@ -139,7 +160,7 @@ func (h ObservedTxOutHandler) handleV1(ctx sdk.Context, msg MsgObservedTxOut) sd
vault, err := h.keeper.GetVault(ctx, tx.ObservedPubKey)
if err != nil {
ctx.Logger().Error("fail to get vault", "error", err)
return sdk.ErrInternal("fail to get vault").Result()
continue
}
vault.SubFunds(tx.Tx.Coins)
vault.OutboundTxCount += 1
......
......@@ -227,3 +227,56 @@ func (s *HandlerObservedTxOutSuite) TestHandle(c *C) {
// make sure the coin has been subtract from the vault
c.Check(ygg.Coins.GetCoin(common.BNBAsset).Amount.Equal(sdk.NewUint(19999962499)), Equals, true, Commentf("%d", ygg.Coins.GetCoin(common.BNBAsset).Amount.Uint64()))
}
func (s *HandlerObservedTxOutSuite) TestHandleStolenFunds(c *C) {
var err error
ctx, _ := setupKeeperForTest(c)
w := getHandlerTestWrapper(c, 1, true, false)
ver := semver.MustParse("0.1.0")
tx := GetRandomTx()
tx.Memo = "I AM A THIEF!" // bad memo
obTx := NewObservedTx(tx, 12, GetRandomPubKey())
obTx.Tx.Coins = common.Coins{
common.NewCoin(common.RuneAsset(), sdk.NewUint(300*common.One)),
common.NewCoin(common.BNBAsset, sdk.NewUint(100*common.One)),
}
txs := ObservedTxs{obTx}
pk := GetRandomPubKey()
c.Assert(err, IsNil)
na := GetRandomNodeAccount(NodeActive)
na.Bond = sdk.NewUint(1000000 * common.One)
na.PubKeySet.Secp256k1 = pk
versionedTxOutStoreDummy := NewVersionedTxOutStoreDummy()
ygg := NewVault(ctx.BlockHeight(), ActiveVault, YggdrasilVault, pk)
ygg.Coins = common.Coins{
common.NewCoin(common.RuneAsset(), sdk.NewUint(500*common.One)),
common.NewCoin(common.BNBAsset, sdk.NewUint(200*common.One)),
}
keeper := &TestObservedTxOutHandleKeeper{
nas: NodeAccounts{na},
voter: NewObservedTxVoter(tx.ID, make(ObservedTxs, 0)),
pool: Pool{
Asset: common.BNBAsset,
BalanceRune: sdk.NewUint(200 * common.One),
BalanceAsset: sdk.NewUint(300 * common.One),
},
yggExists: true,
ygg: ygg,
}
txOutStore, err := versionedTxOutStoreDummy.GetTxOutStore(keeper, ver)
keeper.txOutStore = txOutStore
versionedVaultMgrDummy := NewVersionedVaultMgrDummy(versionedTxOutStoreDummy)
handler := NewObservedTxOutHandler(keeper, versionedTxOutStoreDummy, w.validatorMgr, versionedVaultMgrDummy)
c.Assert(err, IsNil)
msg := NewMsgObservedTxOut(txs, keeper.nas[0].NodeAddress)
result := handler.handle(ctx, msg, ver)
c.Assert(result.IsOK(), Equals, true)
// make sure the coin has been subtract from the vault
c.Check(ygg.Coins.GetCoin(common.BNBAsset).Amount.Equal(sdk.NewUint(9999962500)), Equals, true, Commentf("%d", ygg.Coins.GetCoin(common.BNBAsset).Amount.Uint64()))
c.Assert(keeper.na.Bond.LT(sdk.NewUint(1000000*common.One)), Equals, true, Commentf("%d", keeper.na.Bond.Uint64()))
}
......@@ -126,11 +126,11 @@ func (k *outboundTxHandlerKeeperHelper) GetTxOut(ctx sdk.Context, height int64)
return k.Keeper.GetTxOut(ctx, height)
}
func (k *outboundTxHandlerKeeperHelper) GetNodeAccount(ctx sdk.Context, addr sdk.AccAddress) (NodeAccount, error) {
func (k *outboundTxHandlerKeeperHelper) GetNodeAccountByPubKey(ctx sdk.Context, pk common.PubKey) (NodeAccount, error) {
if k.errGetNodeAccount {
return NodeAccount{}, kaboom
}
return k.Keeper.GetNodeAccount(ctx, addr)
return k.Keeper.GetNodeAccountByPubKey(ctx, pk)
}
func (k *outboundTxHandlerKeeperHelper) GetPool(ctx sdk.Context, asset common.Asset) (Pool, error) {
......
......@@ -72,11 +72,11 @@ func (k *refundTxHandlerKeeperTestHelper) GetTxOut(ctx sdk.Context, height int64
return k.Keeper.GetTxOut(ctx, height)
}
func (k *refundTxHandlerKeeperTestHelper) GetNodeAccount(ctx sdk.Context, addr sdk.AccAddress) (NodeAccount, error) {
func (k *refundTxHandlerKeeperTestHelper) GetNodeAccountByPubKey(ctx sdk.Context, pk common.PubKey) (NodeAccount, error) {
if k.errGetNodeAccount {
return NodeAccount{}, kaboom
}
return k.Keeper.GetNodeAccount(ctx, addr)
return k.Keeper.GetNodeAccountByPubKey(ctx, pk)
}
func (k *refundTxHandlerKeeperTestHelper) GetPool(ctx sdk.Context, asset common.Asset) (Pool, error) {
......
......@@ -29,11 +29,12 @@ func (k yggdrasilTestKeeper) GetAsgardVaultsByStatus(ctx sdk.Context, vs VaultSt
return k.Keeper.GetAsgardVaultsByStatus(ctx, vs)
}
func (k yggdrasilTestKeeper) GetNodeAccount(ctx sdk.Context, addr sdk.AccAddress) (NodeAccount, error) {
func (k yggdrasilTestKeeper) GetNodeAccountByPubKey(ctx sdk.Context, pk common.PubKey) (NodeAccount, error) {
addr, _ := pk.GetThorAddress()
if k.errGetNodeAccount.Equals(addr) {
return NodeAccount{}, kaboom
}
return k.Keeper.GetNodeAccount(ctx, addr)
return k.Keeper.GetNodeAccountByPubKey(ctx, pk)
}
func (k yggdrasilTestKeeper) GetPool(ctx sdk.Context, asset common.Asset) (Pool, error) {
......
......@@ -187,6 +187,8 @@ func (s *MemoSuite) TestParse(c *C) {
c.Assert(err, IsNil)
_, err = ParseMemo("withdraw:bnb:twenty-two") // bad amount
c.Assert(err, NotNil)
_, err = ParseMemo("swap:bnb:STAKER-1:5.6") // bad destination
c.Assert(err, NotNil)
_, err = ParseMemo("swap:bnb:bad_DES:5.6") // bad destination
c.Assert(err, NotNil)
_, err = ParseMemo("swap:bnb:bnb1lejrrtta9cgr49fuh7ktu3sddhe0ff7wenlpn6:five") // bad slip limit
......
......@@ -161,11 +161,7 @@ func slashNodeAccount(ctx sdk.Context, keeper Keeper, observedPubKey common.PubK
if slashAmount.IsZero() {
return nil
}
thorAddr, err := observedPubKey.GetThorAddress()
if err != nil {
return fmt.Errorf("fail to get thoraddress from pubkey(%s) %w", observedPubKey, err)
}
nodeAccount, err := keeper.GetNodeAccount(ctx, thorAddr)
nodeAccount, err := keeper.GetNodeAccountByPubKey(ctx, observedPubKey)
if err != nil {
return fmt.Errorf("fail to get node account with pubkey(%s), %w", observedPubKey, err)
}
......
......@@ -58,6 +58,12 @@ func Fund(ctx sdk.Context, keeper Keeper, txOutStore TxOutStore, constAccessor c
// With 100 Ygg pools, THORNode should check each pool every 8.33 minutes.
na := nodeAccs[ctx.BlockHeight()%int64(len(nodeAccs))]
// check that we have enough bond
minBond := constAccessor.GetInt64Value(constants.MinimumBondInRune)
if na.Bond.LT(sdk.NewUint(uint64(minBond))) {
return nil
}
// figure out if THORNode need to send them assets.
// get a list of coin/amounts this yggdrasil pool should have, ideally.
// TODO: We are assuming here that the pub key is Secp256K1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment