Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func NewApiServer(config config.Config) *ApiServer {
}

solanaRpc := rpc.New(config.SolanaConfig.RpcProviders[0])
rewardAttester := rewards.NewRewardAttester(privateKey, []rewards.Reward{})
rewardAttester := rewards.NewRewardAttester(privateKey, config.Rewards)
transactionSender := spl.NewTransactionSender(
config.SolanaConfig.FeePayers,
config.SolanaConfig.RpcProviders,
Expand Down
131 changes: 84 additions & 47 deletions api/v1_claim_rewards.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"slices"
"strconv"
"strings"
"sync"
"time"

"bridgerton.audius.co/api/dbv1"
Expand Down Expand Up @@ -227,8 +228,7 @@ func getValidatorAttestation(args GetValidatorAttestationParams) (*SenderAttesta
// Gets reward claim attestations from AAO and Validators in parallel.
func fetchAttestations(
ctx context.Context,
rewardClaim rewards.RewardClaim,
handle string,
rewardClaim RewardClaim,
validators []string,
antiAbuseOracle config.Node,
signature string,
Expand All @@ -247,11 +247,11 @@ func fetchAttestations(

if !hasAntiAbuseOracleAttestation {
innerGroup.Go(func() error {
aaoClaim := rewardClaim
aaoClaim := rewardClaim.RewardClaim
aaoClaim.AntiAbuseOracleEthAddress = ""
getAntiAbuseAttestationParams := GetAntiAbuseOracleAttestationParams{
Claim: aaoClaim,
Handle: handle,
Handle: rewardClaim.Handle,
AntiAbuseOracleEndpoint: antiAbuseOracle.Endpoint,
}
aaoAttestation, err := getAntiAbuseOracleAttestation(getAntiAbuseAttestationParams)
Expand All @@ -267,7 +267,7 @@ func fetchAttestations(
innerGroup.Go(func() error {
getValidatorAttestationParams := GetValidatorAttestationParams{
Validator: validator,
Claim: rewardClaim,
Claim: rewardClaim.RewardClaim,
UserEthAddress: rewardClaim.RecipientEthAddress,
Signature: signature,
}
Expand All @@ -291,10 +291,9 @@ func fetchAttestations(
// Builds a Solana transaction to claim a reward from the attestations and sends it with retries.
func sendRewardClaimTransactions(
ctx context.Context,
userBank solana.PublicKey,
rewardManagerClient *reward_manager.RewardManagerClient,
transactionSender *spl.TransactionSender,
rewardClaim rewards.RewardClaim,
rewardClaim RewardClaim,
attestations []SenderAttestation,
) ([]solana.Signature, error) {
tx := solana.NewTransactionBuilder()
Expand Down Expand Up @@ -379,7 +378,7 @@ func sendRewardClaimTransactions(
common.HexToAddress(rewardClaim.AntiAbuseOracleEthAddress),
rewardManagerClient.GetProgramStateAccount(),
state.TokenAccount,
userBank,
rewardClaim.UserBank,
feePayer.PublicKey(),
).Build()
tx.AddInstruction(evaluateAttestationInstruction)
Expand Down Expand Up @@ -412,29 +411,20 @@ type RelayTransactionResponse struct {
// Claims an individual reward.
func claimReward(
ctx context.Context,
rewardClaim RewardClaim,
rewardManagerClient *reward_manager.RewardManagerClient,
row dbv1.GetUndisbursedChallengesRow,
userBank solana.PublicKey,
antiAbuseOracle config.Node,
rewardAttester *rewards.RewardAttester,
transactionSender *spl.TransactionSender,
antiAbuseOracle config.Node,
validators []config.Node,
) ([]solana.Signature, error) {
rewardClaim := rewards.RewardClaim{
RewardID: row.ChallengeID,
Amount: uint64(1000), // TODO: Change me!
Specifier: row.Specifier,
RecipientEthAddress: row.Wallet.String,
AntiAbuseOracleEthAddress: antiAbuseOracle.DelegateOwnerWallet,
}
handle := row.Handle.String

rewardManagerStateData, err := rewardManagerClient.GetProgramState(ctx)
if err != nil {
return nil, err
}

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

// Attest from Bridge to get authority signature
_, signature, err := rewardAttester.Attest(rewardClaim)
_, signature, err := rewardAttester.Attest(rewardClaim.RewardClaim)
if err != nil {
return nil, err
}
Expand All @@ -469,7 +459,6 @@ func claimReward(
attestations, err := fetchAttestations(
ctx,
rewardClaim,
handle,
selectedValidators,
antiAbuseOracle,
signature,
Expand All @@ -482,7 +471,6 @@ func claimReward(
// Build and send solana transactions
signatures, err := sendRewardClaimTransactions(
ctx,
userBank,
rewardManagerClient,
transactionSender,
rewardClaim,
Expand Down Expand Up @@ -537,6 +525,29 @@ func getAntiAbuseOracle(antiAbuseOracleEndpoints []string) (node *config.Node, e
}, nil
}

func getReward(rewardId string, rewardsList []rewards.Reward) (rewards.Reward, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could preload these into a map instead 🤷

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list small, should be fineee

for _, r := range rewardsList {
if r.RewardId == rewardId {
return r, nil
}
}
return rewards.Reward{}, fmt.Errorf("challenge ID %s does not have a configured reward", rewardId)
}

type RewardClaim struct {
rewards.RewardClaim
Handle string
UserBank solana.PublicKey
}

type ClaimResult struct {
ChallengeID string `json:"challengeId"`
Specifier string `json:"specifier"`
Amount uint64 `json:"amount"`
Signatures []solana.Signature `json:"signatures"`
Error string `json:"error,omitempty"`
}

// Claims all the filtered undisbursed rewards for a user.
func (api *ApiServer) v1ClaimRewards(c *fiber.Ctx) error {
challengeId := c.Query("challenge_id")
Expand Down Expand Up @@ -573,26 +584,57 @@ func (api *ApiServer) v1ClaimRewards(c *fiber.Ctx) error {
return err
}

signatures := make(map[string]map[string][]solana.Signature)
bankAccount, err := claimable_tokens.DeriveUserBankAccount(
api.solanaConfig.MintAudio,
undisbursedRows[0].Wallet.String,
)
if err != nil {
return err
}

results := make([]ClaimResult, len(undisbursedRows))
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
g, ctx := errgroup.WithContext(ctx)
for _, row := range undisbursedRows {
bankAccount, err := claimable_tokens.DeriveUserBankAccount(api.solanaConfig.MintAudio, row.Wallet.String)
if err != nil {
return err
}
g.Go(func() error {
sig, err := claimReward(
g := &sync.WaitGroup{}
g.Add(len(undisbursedRows))
for i, row := range undisbursedRows {
go func() {
results[i] = ClaimResult{
ChallengeID: row.ChallengeID,
Specifier: row.Specifier,
}

reward, err := getReward(row.ChallengeID, api.rewardAttester.Rewards)
if err != nil {
results[i].Error = err.Error()
g.Done()
return
}

results[i].Amount = reward.Amount

rewardClaim := RewardClaim{
RewardClaim: rewards.RewardClaim{
RewardID: row.ChallengeID,
Amount: reward.Amount,
Specifier: row.Specifier,
RecipientEthAddress: row.Wallet.String,
AntiAbuseOracleEthAddress: antiAbuseOracle.DelegateOwnerWallet,
},
Handle: row.Handle.String,
UserBank: bankAccount,
}

sigs, err := claimReward(
ctx,
rewardClaim,
&api.rewardManagerClient,
row,
bankAccount,
*antiAbuseOracle,
&api.rewardAttester,
&api.transactionSender,
*antiAbuseOracle,
api.validators,
)

if err != nil {
var instrErr *spl.InstructionError
if errors.As(err, &instrErr) {
Expand All @@ -612,21 +654,16 @@ func (api *ApiServer) v1ClaimRewards(c *fiber.Ctx) error {
zap.Error(err),
)
}
return err
}
if signatures[row.ChallengeID] == nil {
signatures[row.ChallengeID] = make(map[string][]solana.Signature)
results[i].Error = err.Error()
}
signatures[row.ChallengeID][row.Specifier] = sig
return nil
})
}
err = g.Wait()
if err != nil {
return err

results[i].Signatures = sigs
g.Done()
}()
}
g.Wait()

return c.Status(http.StatusOK).JSON(fiber.Map{
"data": signatures,
"data": results,
})
}
6 changes: 6 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"log"
"os"

core_config "github.com/AudiusProject/audiusd/pkg/core/config"
"github.com/AudiusProject/audiusd/pkg/rewards"
_ "github.com/joho/godotenv/autoload"
)

Expand All @@ -20,6 +22,7 @@ type Config struct {
StakingBridgeUsdcPayoutWallet string
SolanaConfig SolanaConfig
AntiAbuseOracles []string
Rewards []rewards.Reward
}

var Cfg = Config{
Expand All @@ -45,6 +48,7 @@ func init() {
Cfg.Nodes = DevNodes
// Dummy key
Cfg.DelegatePrivateKey = "13422b9affd75ff80f94f1ea394e6a6097830cb58cda2d3542f37464ecaee7df"
Cfg.Rewards = core_config.MakeRewards(core_config.DevClaimAuthorities, core_config.DevRewardExtensions)
case "stage":
fallthrough
case "staging":
Expand All @@ -60,6 +64,7 @@ func init() {
}
Cfg.Nodes = StageNodes
Cfg.DeadNodes = []string{}
Cfg.Rewards = core_config.MakeRewards(core_config.StageClaimAuthorities, core_config.StageRewardExtensions)
case "prod":
fallthrough
case "production":
Expand All @@ -76,6 +81,7 @@ func init() {
Cfg.DeadNodes = []string{
"https://content.grassfed.network",
}
Cfg.Rewards = core_config.MakeRewards(core_config.ProdClaimAuthorities, core_config.ProdRewardExtensions)
default:
log.Fatalf("Unknown environment: %s", env)
}
Expand Down