Skip to content

Commit c664a32

Browse files
authored
Rewards: Use core config to get amounts, refactor a bit to allow partial success (#60)
- get reward amounts from core config - use a waitgroup instead of error group to allow for all the goroutines to finish and partially succeed - use struct embedding to include handle/userBank in the claim struct - change response structure to a list or challenges with their signatures or errors
1 parent 567dc19 commit c664a32

3 files changed

Lines changed: 91 additions & 48 deletions

File tree

api/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func NewApiServer(config config.Config) *ApiServer {
121121
}
122122

123123
solanaRpc := rpc.New(config.SolanaConfig.RpcProviders[0])
124-
rewardAttester := rewards.NewRewardAttester(privateKey, []rewards.Reward{})
124+
rewardAttester := rewards.NewRewardAttester(privateKey, config.Rewards)
125125
transactionSender := spl.NewTransactionSender(
126126
config.SolanaConfig.FeePayers,
127127
config.SolanaConfig.RpcProviders,

api/v1_claim_rewards.go

Lines changed: 84 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"slices"
1616
"strconv"
1717
"strings"
18+
"sync"
1819
"time"
1920

2021
"bridgerton.audius.co/api/dbv1"
@@ -227,8 +228,7 @@ func getValidatorAttestation(args GetValidatorAttestationParams) (*SenderAttesta
227228
// Gets reward claim attestations from AAO and Validators in parallel.
228229
func fetchAttestations(
229230
ctx context.Context,
230-
rewardClaim rewards.RewardClaim,
231-
handle string,
231+
rewardClaim RewardClaim,
232232
validators []string,
233233
antiAbuseOracle config.Node,
234234
signature string,
@@ -247,11 +247,11 @@ func fetchAttestations(
247247

248248
if !hasAntiAbuseOracleAttestation {
249249
innerGroup.Go(func() error {
250-
aaoClaim := rewardClaim
250+
aaoClaim := rewardClaim.RewardClaim
251251
aaoClaim.AntiAbuseOracleEthAddress = ""
252252
getAntiAbuseAttestationParams := GetAntiAbuseOracleAttestationParams{
253253
Claim: aaoClaim,
254-
Handle: handle,
254+
Handle: rewardClaim.Handle,
255255
AntiAbuseOracleEndpoint: antiAbuseOracle.Endpoint,
256256
}
257257
aaoAttestation, err := getAntiAbuseOracleAttestation(getAntiAbuseAttestationParams)
@@ -267,7 +267,7 @@ func fetchAttestations(
267267
innerGroup.Go(func() error {
268268
getValidatorAttestationParams := GetValidatorAttestationParams{
269269
Validator: validator,
270-
Claim: rewardClaim,
270+
Claim: rewardClaim.RewardClaim,
271271
UserEthAddress: rewardClaim.RecipientEthAddress,
272272
Signature: signature,
273273
}
@@ -291,10 +291,9 @@ func fetchAttestations(
291291
// Builds a Solana transaction to claim a reward from the attestations and sends it with retries.
292292
func sendRewardClaimTransactions(
293293
ctx context.Context,
294-
userBank solana.PublicKey,
295294
rewardManagerClient *reward_manager.RewardManagerClient,
296295
transactionSender *spl.TransactionSender,
297-
rewardClaim rewards.RewardClaim,
296+
rewardClaim RewardClaim,
298297
attestations []SenderAttestation,
299298
) ([]solana.Signature, error) {
300299
tx := solana.NewTransactionBuilder()
@@ -379,7 +378,7 @@ func sendRewardClaimTransactions(
379378
common.HexToAddress(rewardClaim.AntiAbuseOracleEthAddress),
380379
rewardManagerClient.GetProgramStateAccount(),
381380
state.TokenAccount,
382-
userBank,
381+
rewardClaim.UserBank,
383382
feePayer.PublicKey(),
384383
).Build()
385384
tx.AddInstruction(evaluateAttestationInstruction)
@@ -412,29 +411,20 @@ type RelayTransactionResponse struct {
412411
// Claims an individual reward.
413412
func claimReward(
414413
ctx context.Context,
414+
rewardClaim RewardClaim,
415415
rewardManagerClient *reward_manager.RewardManagerClient,
416-
row dbv1.GetUndisbursedChallengesRow,
417-
userBank solana.PublicKey,
418-
antiAbuseOracle config.Node,
419416
rewardAttester *rewards.RewardAttester,
420417
transactionSender *spl.TransactionSender,
418+
antiAbuseOracle config.Node,
421419
validators []config.Node,
422420
) ([]solana.Signature, error) {
423-
rewardClaim := rewards.RewardClaim{
424-
RewardID: row.ChallengeID,
425-
Amount: uint64(1000), // TODO: Change me!
426-
Specifier: row.Specifier,
427-
RecipientEthAddress: row.Wallet.String,
428-
AntiAbuseOracleEthAddress: antiAbuseOracle.DelegateOwnerWallet,
429-
}
430-
handle := row.Handle.String
431421

432422
rewardManagerStateData, err := rewardManagerClient.GetProgramState(ctx)
433423
if err != nil {
434424
return nil, err
435425
}
436426

437-
attestationsData, err := rewardManagerClient.GetSubmittedAttestations(ctx, rewardClaim)
427+
attestationsData, err := rewardManagerClient.GetSubmittedAttestations(ctx, rewardClaim.RewardClaim)
438428
if err != nil {
439429
// If not found, then it's empty. Use default values for the purpose
440430
// of getting an empty list of messages
@@ -455,7 +445,7 @@ func claimReward(
455445
}
456446

457447
// Attest from Bridge to get authority signature
458-
_, signature, err := rewardAttester.Attest(rewardClaim)
448+
_, signature, err := rewardAttester.Attest(rewardClaim.RewardClaim)
459449
if err != nil {
460450
return nil, err
461451
}
@@ -469,7 +459,6 @@ func claimReward(
469459
attestations, err := fetchAttestations(
470460
ctx,
471461
rewardClaim,
472-
handle,
473462
selectedValidators,
474463
antiAbuseOracle,
475464
signature,
@@ -482,7 +471,6 @@ func claimReward(
482471
// Build and send solana transactions
483472
signatures, err := sendRewardClaimTransactions(
484473
ctx,
485-
userBank,
486474
rewardManagerClient,
487475
transactionSender,
488476
rewardClaim,
@@ -537,6 +525,29 @@ func getAntiAbuseOracle(antiAbuseOracleEndpoints []string) (node *config.Node, e
537525
}, nil
538526
}
539527

528+
func getReward(rewardId string, rewardsList []rewards.Reward) (rewards.Reward, error) {
529+
for _, r := range rewardsList {
530+
if r.RewardId == rewardId {
531+
return r, nil
532+
}
533+
}
534+
return rewards.Reward{}, fmt.Errorf("challenge ID %s does not have a configured reward", rewardId)
535+
}
536+
537+
type RewardClaim struct {
538+
rewards.RewardClaim
539+
Handle string
540+
UserBank solana.PublicKey
541+
}
542+
543+
type ClaimResult struct {
544+
ChallengeID string `json:"challengeId"`
545+
Specifier string `json:"specifier"`
546+
Amount uint64 `json:"amount"`
547+
Signatures []solana.Signature `json:"signatures"`
548+
Error string `json:"error,omitempty"`
549+
}
550+
540551
// Claims all the filtered undisbursed rewards for a user.
541552
func (api *ApiServer) v1ClaimRewards(c *fiber.Ctx) error {
542553
challengeId := c.Query("challenge_id")
@@ -573,26 +584,57 @@ func (api *ApiServer) v1ClaimRewards(c *fiber.Ctx) error {
573584
return err
574585
}
575586

576-
signatures := make(map[string]map[string][]solana.Signature)
587+
bankAccount, err := claimable_tokens.DeriveUserBankAccount(
588+
api.solanaConfig.MintAudio,
589+
undisbursedRows[0].Wallet.String,
590+
)
591+
if err != nil {
592+
return err
593+
}
594+
595+
results := make([]ClaimResult, len(undisbursedRows))
577596
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
578597
defer cancel()
579-
g, ctx := errgroup.WithContext(ctx)
580-
for _, row := range undisbursedRows {
581-
bankAccount, err := claimable_tokens.DeriveUserBankAccount(api.solanaConfig.MintAudio, row.Wallet.String)
582-
if err != nil {
583-
return err
584-
}
585-
g.Go(func() error {
586-
sig, err := claimReward(
598+
g := &sync.WaitGroup{}
599+
g.Add(len(undisbursedRows))
600+
for i, row := range undisbursedRows {
601+
go func() {
602+
results[i] = ClaimResult{
603+
ChallengeID: row.ChallengeID,
604+
Specifier: row.Specifier,
605+
}
606+
607+
reward, err := getReward(row.ChallengeID, api.rewardAttester.Rewards)
608+
if err != nil {
609+
results[i].Error = err.Error()
610+
g.Done()
611+
return
612+
}
613+
614+
results[i].Amount = reward.Amount
615+
616+
rewardClaim := RewardClaim{
617+
RewardClaim: rewards.RewardClaim{
618+
RewardID: row.ChallengeID,
619+
Amount: reward.Amount,
620+
Specifier: row.Specifier,
621+
RecipientEthAddress: row.Wallet.String,
622+
AntiAbuseOracleEthAddress: antiAbuseOracle.DelegateOwnerWallet,
623+
},
624+
Handle: row.Handle.String,
625+
UserBank: bankAccount,
626+
}
627+
628+
sigs, err := claimReward(
587629
ctx,
630+
rewardClaim,
588631
&api.rewardManagerClient,
589-
row,
590-
bankAccount,
591-
*antiAbuseOracle,
592632
&api.rewardAttester,
593633
&api.transactionSender,
634+
*antiAbuseOracle,
594635
api.validators,
595636
)
637+
596638
if err != nil {
597639
var instrErr *spl.InstructionError
598640
if errors.As(err, &instrErr) {
@@ -612,21 +654,16 @@ func (api *ApiServer) v1ClaimRewards(c *fiber.Ctx) error {
612654
zap.Error(err),
613655
)
614656
}
615-
return err
616-
}
617-
if signatures[row.ChallengeID] == nil {
618-
signatures[row.ChallengeID] = make(map[string][]solana.Signature)
657+
results[i].Error = err.Error()
619658
}
620-
signatures[row.ChallengeID][row.Specifier] = sig
621-
return nil
622-
})
623-
}
624-
err = g.Wait()
625-
if err != nil {
626-
return err
659+
660+
results[i].Signatures = sigs
661+
g.Done()
662+
}()
627663
}
664+
g.Wait()
628665

629666
return c.Status(http.StatusOK).JSON(fiber.Map{
630-
"data": signatures,
667+
"data": results,
631668
})
632669
}

config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"log"
55
"os"
66

7+
core_config "github.com/AudiusProject/audiusd/pkg/core/config"
8+
"github.com/AudiusProject/audiusd/pkg/rewards"
79
_ "github.com/joho/godotenv/autoload"
810
)
911

@@ -20,6 +22,7 @@ type Config struct {
2022
StakingBridgeUsdcPayoutWallet string
2123
SolanaConfig SolanaConfig
2224
AntiAbuseOracles []string
25+
Rewards []rewards.Reward
2326
}
2427

2528
var Cfg = Config{
@@ -45,6 +48,7 @@ func init() {
4548
Cfg.Nodes = DevNodes
4649
// Dummy key
4750
Cfg.DelegatePrivateKey = "13422b9affd75ff80f94f1ea394e6a6097830cb58cda2d3542f37464ecaee7df"
51+
Cfg.Rewards = core_config.MakeRewards(core_config.DevClaimAuthorities, core_config.DevRewardExtensions)
4852
case "stage":
4953
fallthrough
5054
case "staging":
@@ -60,6 +64,7 @@ func init() {
6064
}
6165
Cfg.Nodes = StageNodes
6266
Cfg.DeadNodes = []string{}
67+
Cfg.Rewards = core_config.MakeRewards(core_config.StageClaimAuthorities, core_config.StageRewardExtensions)
6368
case "prod":
6469
fallthrough
6570
case "production":
@@ -76,6 +81,7 @@ func init() {
7681
Cfg.DeadNodes = []string{
7782
"https://content.grassfed.network",
7883
}
84+
Cfg.Rewards = core_config.MakeRewards(core_config.ProdClaimAuthorities, core_config.ProdRewardExtensions)
7985
default:
8086
log.Fatalf("Unknown environment: %s", env)
8187
}

0 commit comments

Comments
 (0)