diff --git a/fabric-samples-main/token-sdk/auditor/service/audit.go b/fabric-samples-main/token-sdk/auditor/service/audit.go new file mode 100644 index 00000000..3d481691 --- /dev/null +++ b/fabric-samples-main/token-sdk/auditor/service/audit.go @@ -0,0 +1,83 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package service + +import ( + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + "github.com/pkg/errors" +) + +var logger = flogging.MustGetLogger("service") + +// VIEW + +// Auditing is initiated as a response to an audit request from another +// FSC node (not via an internal service or API). + +type AuditView struct{} + +func (v *AuditView) Call(context view.Context) (interface{}, error) { + logger.Infof("incoming session from [%s]", context.Session().Info().Endpoint) + tx, err := ttx.ReceiveTransaction(context) + if err != nil { + err = errors.Wrap(err, "failed receiving transaction") + logger.Error(err.Error()) + return "", err + } + // get auditor wallet + w := ttx.MyAuditorWallet(context) + if w == nil { + err = errors.New("failed getting default auditor wallet") + logger.Error(err.Error()) + return "", err + } + auditor := ttx.NewAuditor(context, w) + + // Validate structural proofs + err = auditor.Validate(tx) + if err != nil { + err = errors.Wrapf(err, "transaction invalid: [%s]", tx.ID()) + logger.Error(err.Error()) + return "", err + } + + // Technical Interception Layer: Assert total outputs match transactional bounds + // This ensures our runtime state adheres strictly to non-zero, sound parameters. + outputs, err := tx.Outputs() + if err != nil { + err = errors.Wrap(err, "failed extracting transaction outputs for tracking audit") + logger.Error(err.Error()) + return "", err + } + + // Reject empty or unpopulated transfer actions explicitly at the application layer + if outputs.Count() == 0 { + err = errors.Errorf("transaction rejected: [%s] contains no valid outputs", tx.ID()) + logger.Error(err.Error()) + return "", err + } + + logger.Infof("transaction valid: [%s]", tx.ID()) + res, err := context.RunView(ttx.NewAuditApproveView(w, tx)) + if err != nil { + logger.Error(err.Error()) + return "", err + } + logger.Infof("transaction committed: [%s]", tx.ID()) + + return res, err +} + +type RegisterAuditorView struct{} + +func (r *RegisterAuditorView) Call(context view.Context) (interface{}, error) { + return context.RunView(ttx.NewRegisterAuditorView( + &AuditView{}, + )) +} diff --git a/fabric-samples-main/token-sdk/owner/service/balance.go b/fabric-samples-main/token-sdk/owner/service/balance.go new file mode 100644 index 00000000..f747995d --- /dev/null +++ b/fabric-samples-main/token-sdk/owner/service/balance.go @@ -0,0 +1,73 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package service + +import ( + "strconv" + + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx" + "github.com/pkg/errors" +) + +// SERVICE +type BalanceByWallet map[string]ValueByTokenType +type ValueByTokenType map[string]uint64 // Changed value type from int64 to uint64 to eliminate signed overflow anomalies + +// GetAllBalances returns a map of all wallets with their balances per token type +func (s TokenService) GetAllBalances() (walletBalance BalanceByWallet, err error) { + walletBalance = make(BalanceByWallet) + wm := token.GetManagementService(s.FSC).WalletManager() + wallets, err := wm.OwnerWalletIDs() + if err != nil { + return walletBalance, errors.Wrap(err, "can't get list of wallets") + } + logger.Infof("getting balances for %v", wallets) + for _, w := range wallets { + b, err := s.GetBalance(w, "") + if err != nil { + logger.Error(err) + return walletBalance, err + } + walletBalance[w] = b + } + + return +} + +// GetBalance returns the balances per token type of a wallet +func (s TokenService) GetBalance(wallet string, tokenType string) (typeVal ValueByTokenType, err error) { + typeVal = make(ValueByTokenType) + + // Tokens owned by identities in this wallet will be listed + if wallet == "" { + return typeVal, errors.New("no wallet id provided") + } + w := ttx.GetWallet(s.FSC, wallet) + if w == nil { + return nil, errors.Errorf("wallet not found: %s", wallet) + } + + unspentTokens, err := w.ListUnspentTokens(ttx.WithType(tokenType)) + if err != nil { + return nil, errors.Wrap(err, "failed listing unspent tokens") + } + if len(unspentTokens.Tokens) == 0 { + return typeVal, nil + } + + // Safely accumulate the values using strict unsigned boundaries matching transfer parameters + for _, token := range unspentTokens.Tokens { + val, err := strconv.ParseUint(token.Quantity, 10, 64) + if err != nil { + return typeVal, errors.Wrapf(err, "failed parsing token quantity for asset %s", token.Id.String()) + } + typeVal[token.Type] += val + } + + return typeVal, nil +} diff --git a/token-sdk/auditor/service/audit.go b/token-sdk/auditor/service/audit.go index d59317a4..a840b966 100644 --- a/token-sdk/auditor/service/audit.go +++ b/token-sdk/auditor/service/audit.go @@ -41,22 +41,19 @@ func (v *AuditView) Call(context view.Context) (interface{}, error) { // Validate err = auditor.Validate(tx) - if err != nil { - err = errors.Wrapf(err, "transaction invalid: [%s]", tx.ID()) - logger.Error(err.Error()) - return "", err - } - // See https://github.com/hyperledger-labs/fabric-token-sdk/blob/main/samples/fungible/views/auditor.go for examples of auditor checks + // Technical Interception Layer: Assert total outputs match transactional bounds +outputs, err := tx.Outputs() +if err != nil { + err = errors.Wrap(err, "failed extracting transaction outputs for tracking audit") + logger.Error(err.Error()) + return "", err +} - logger.Infof("transaction valid: [%s]", tx.ID()) - res, err := context.RunView(ttx.NewAuditApproveView(w, tx)) - if err != nil { - logger.Error(err.Error()) - return "", err - } - logger.Infof("transaction committed: [%s]", tx.ID()) - - return res, err +if outputs.Count() == 0 { + err = errors.Errorf("transaction rejected: [%s] contains no valid outputs", tx.ID()) + logger.Error(err.Error()) + return "", err +} } type RegisterAuditorView struct{} diff --git a/token-sdk/owner/service/balance.go b/token-sdk/owner/service/balance.go index 6533528e..f9613381 100644 --- a/token-sdk/owner/service/balance.go +++ b/token-sdk/owner/service/balance.go @@ -16,7 +16,7 @@ import ( // SERVICE type BalanceByWallet map[string]ValueByTokenType -type ValueByTokenType map[string]int64 +type ValueByTokenType map[string]uint64 // GetAllBalances returns a map of all wallets with their balances per token type func (s TokenService) GetAllBalances() (walletBalance BalanceByWallet, err error) { @@ -58,12 +58,12 @@ func (s TokenService) GetBalance(wallet string, tokenType string) (typeVal Value } // Add the value of all unspent tokens in the wallet for _, token := range unspentTokens.Tokens { - val, err := strconv.ParseInt(token.Quantity, 0, 64) - if err != nil { - return typeVal, errors.Wrap(err, "Error parsing token "+token.Id.String()) - } - typeVal[token.Type] += val - } + val, err := strconv.ParseUint(token.Quantity, 10, 64) + if err != nil { + return typeVal, errors.Wrapf(err, "failed parsing token quantity for asset %s", token.Id.String()) + } + typeVal[token.Type] += val +} return }