From c1721744e168cd43df246f064ada867e581cabe6 Mon Sep 17 00:00:00 2001 From: hundredark Date: Fri, 1 Aug 2025 16:49:19 +0800 Subject: [PATCH 1/4] should check the balance of deployed asset from mtg --- solana/node.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/solana/node.go b/solana/node.go index 8f40228..5fadc41 100644 --- a/solana/node.go +++ b/solana/node.go @@ -3,6 +3,7 @@ package solana import ( "context" "fmt" + "math" "slices" "sort" "strconv" @@ -17,6 +18,8 @@ import ( "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/mtg" "github.com/fox-one/mixin-sdk-go/v2" + "github.com/gagliardetto/solana-go" + "github.com/shopspring/decimal" ) type Node struct { @@ -68,10 +71,32 @@ func (node *Node) Boot(ctx context.Context, version string) { } go node.bootObserver(ctx, version) go node.bootSigner(ctx) + go node.mtgBalanceCheckLoop(ctx) logger.Printf("node.Boot(%s, %d)", node.id, node.Index()) } +func (node *Node) mtgBalanceCheckLoop(ctx context.Context) { + for { + as, err := node.store.ListDeployedAssets(ctx) + if err != nil { + panic(err) + } + for _, a := range as { + mint, err := node.solana.GetMint(ctx, solana.MPK(a.Address)) + if err != nil || mint == nil { + panic(fmt.Errorf("solana.GetMint(%s) => %v %v", a.Address, mint, err)) + } + supply := decimal.New(int64(mint.Supply), -int32(mint.Decimals)) + balance := node.getAssetBalanceAt(ctx, math.MaxInt64, a.AssetId) + if balance.Cmp(supply) < 0 { + panic(fmt.Errorf("invalid balance of %s %s: %s %s", a.AssetId, a.Address, balance, supply)) + } + } + time.Sleep(time.Second) + } +} + func (node *Node) Index() int { index := node.findMember(string(node.id)) if index < 0 { From 9dd39236c8f584293157595e1d06e8598b3e6148 Mon Sep 17 00:00:00 2001 From: hundredark Date: Fri, 1 Aug 2025 17:06:03 +0800 Subject: [PATCH 2/4] check the balance of deployed asset when mtg build transaction --- solana/node.go | 22 ++++++++++------------ solana/observer.go | 6 +++--- solana/rpc.go | 10 ++++++++++ solana/transaction.go | 12 ++++++++++++ 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/solana/node.go b/solana/node.go index 5fadc41..4cff9e6 100644 --- a/solana/node.go +++ b/solana/node.go @@ -3,7 +3,6 @@ package solana import ( "context" "fmt" - "math" "slices" "sort" "strconv" @@ -18,8 +17,6 @@ import ( "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/mtg" "github.com/fox-one/mixin-sdk-go/v2" - "github.com/gagliardetto/solana-go" - "github.com/shopspring/decimal" ) type Node struct { @@ -83,20 +80,21 @@ func (node *Node) mtgBalanceCheckLoop(ctx context.Context) { panic(err) } for _, a := range as { - mint, err := node.solana.GetMint(ctx, solana.MPK(a.Address)) - if err != nil || mint == nil { - panic(fmt.Errorf("solana.GetMint(%s) => %v %v", a.Address, mint, err)) - } - supply := decimal.New(int64(mint.Supply), -int32(mint.Decimals)) - balance := node.getAssetBalanceAt(ctx, math.MaxInt64, a.AssetId) - if balance.Cmp(supply) < 0 { - panic(fmt.Errorf("invalid balance of %s %s: %s %s", a.AssetId, a.Address, balance, supply)) - } + node.checkMintBalance(ctx, a) + time.Sleep(100 * time.Millisecond) } time.Sleep(time.Second) } } +func (node *Node) checkMintBalance(ctx context.Context, a *solanaApp.DeployedAsset) { + supply := node.RPCMintSupply(ctx, a.Address) + balance := node.getMtgAssetBalance(ctx, a.AssetId) + if balance.Cmp(supply) < 0 { + panic(fmt.Errorf("invalid balance of mtg asset %s %s: %s %s", a.AssetId, a.Address, balance, supply)) + } +} + func (node *Node) Index() int { index := node.findMember(string(node.id)) if index < 0 { diff --git a/solana/observer.go b/solana/observer.go index c56c698..662f6fb 100644 --- a/solana/observer.go +++ b/solana/observer.go @@ -1016,7 +1016,7 @@ func (node *Node) checkSufficientBalanceForBurnSystemCall(ctx context.Context, c panic(err) } amount := decimal.New(int64(*burn.Amount), -int32(da.Decimals)) - balance := node.getAssetBalanceAt(ctx, math.MaxInt64, da.AssetId) + balance := node.getMtgAssetBalance(ctx, da.AssetId) if balance.Cmp(amount) < 0 { logger.Printf("insufficient balance to confirm burn system call: %s %s %s %s", call.RequestId, da.AssetId, amount.String(), balance.String()) return false @@ -1025,8 +1025,8 @@ func (node *Node) checkSufficientBalanceForBurnSystemCall(ctx context.Context, c return true } -func (node *Node) getAssetBalanceAt(ctx context.Context, sequence uint64, assetId string) decimal.Decimal { - os := node.group.ListOutputsForAsset(ctx, node.conf.AppId, assetId, node.conf.MTG.Genesis.Epoch, sequence, mtg.SafeUtxoStateUnspent, 0) +func (node *Node) getMtgAssetBalance(ctx context.Context, assetId string) decimal.Decimal { + os := node.group.ListOutputsForAsset(ctx, node.conf.AppId, assetId, node.conf.MTG.Genesis.Epoch, math.MaxInt64, mtg.SafeUtxoStateUnspent, 0) total := decimal.NewFromInt(0) for _, o := range os { total = total.Add(o.Amount) diff --git a/solana/rpc.go b/solana/rpc.go index 7c94989..47891d9 100644 --- a/solana/rpc.go +++ b/solana/rpc.go @@ -17,6 +17,7 @@ import ( "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/programs/token" "github.com/gagliardetto/solana-go/rpc" + "github.com/shopspring/decimal" ) func (node *Node) checkCreatedAtaUntilSufficient(ctx context.Context, tx *solana.Transaction) error { @@ -253,6 +254,15 @@ func (node *Node) RPCCheckNFT(ctx context.Context, account string) (bool, error) return tm.Supply == 1 && tm.Decimals == 0, nil } +func (node *Node) RPCMintSupply(ctx context.Context, account string) decimal.Decimal { + mint, err := node.solana.GetMint(ctx, solana.MPK(account)) + if err != nil || mint == nil { + panic(fmt.Errorf("solana.GetMint(%s) => %v %v", account, mint, err)) + } + supply := decimal.New(int64(mint.Supply), -int32(mint.Decimals)) + return supply +} + func (node *Node) RPCGetMinimumBalanceForRentExemption(ctx context.Context, dataSize uint64) (uint64, error) { key := fmt.Sprintf("getMinimumBalanceForRentExemption:%d", dataSize) value, err := node.store.ReadCache(ctx, key) diff --git a/solana/transaction.go b/solana/transaction.go index bbb8d3e..b07802d 100644 --- a/solana/transaction.go +++ b/solana/transaction.go @@ -60,6 +60,18 @@ func (node *Node) checkTransaction(ctx context.Context, act *mtg.Action, assetId return "" } + da, err := node.store.ReadDeployedAsset(ctx, assetId) + if err != nil { + panic(err) + } + if da != nil { + supply := node.RPCMintSupply(ctx, da.Address) + balance = node.getMtgAssetBalance(ctx, da.AssetId) + if balance.Sub(amt).Cmp(supply) < 0 { + panic(fmt.Errorf("invalid balance of mtg asset %s %s: %s %s %s", da.AssetId, da.Address, balance, amt, supply)) + } + } + nextId := common.UniqueId(node.group.GenesisId(), traceId) logger.Printf("node.checkTransaction(%s) => %s", traceId, nextId) return nextId From 0312c9f459a574ffa2663c31e2c4534360b1186a Mon Sep 17 00:00:00 2001 From: hundredark Date: Fri, 1 Aug 2025 17:14:59 +0800 Subject: [PATCH 3/4] fix interval --- solana/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solana/node.go b/solana/node.go index 4cff9e6..989f81e 100644 --- a/solana/node.go +++ b/solana/node.go @@ -83,7 +83,7 @@ func (node *Node) mtgBalanceCheckLoop(ctx context.Context) { node.checkMintBalance(ctx, a) time.Sleep(100 * time.Millisecond) } - time.Sleep(time.Second) + time.Sleep(time.Minute) } } From 2d743a470b1efc6dd5767dae7c4aa33c2e97bf8c Mon Sep 17 00:00:00 2001 From: hundredark Date: Mon, 4 Aug 2025 10:35:01 +0800 Subject: [PATCH 4/4] fix test --- solana/transaction.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/solana/transaction.go b/solana/transaction.go index b07802d..fb386dc 100644 --- a/solana/transaction.go +++ b/solana/transaction.go @@ -60,15 +60,17 @@ func (node *Node) checkTransaction(ctx context.Context, act *mtg.Action, assetId return "" } - da, err := node.store.ReadDeployedAsset(ctx, assetId) - if err != nil { - panic(err) - } - if da != nil { - supply := node.RPCMintSupply(ctx, da.Address) - balance = node.getMtgAssetBalance(ctx, da.AssetId) - if balance.Sub(amt).Cmp(supply) < 0 { - panic(fmt.Errorf("invalid balance of mtg asset %s %s: %s %s %s", da.AssetId, da.Address, balance, amt, supply)) + if !common.CheckTestEnvironment(ctx) { + da, err := node.store.ReadDeployedAsset(ctx, assetId) + if err != nil { + panic(err) + } + if da != nil { + supply := node.RPCMintSupply(ctx, da.Address) + balance = node.getMtgAssetBalance(ctx, da.AssetId) + if balance.Sub(amt).Cmp(supply) < 0 { + panic(fmt.Errorf("invalid balance of mtg asset %s %s: %s %s %s", da.AssetId, da.Address, balance, amt, supply)) + } } }