Skip to content

Commit c6bfb4b

Browse files
authored
Handle already submitted validator attestations (#54)
- Adds support for decoding of attestations account - Adds owner wallets to node list - Fetches existing attestations, selects validators with new owners
1 parent 1d3c9f4 commit c6bfb4b

10 files changed

Lines changed: 1130 additions & 192 deletions

File tree

api/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ func NewApiServer(config config.Config) *ApiServer {
132132
rewardAttester: *rewards.NewRewardAttester(privateKey, []rewards.Reward{}),
133133
solanaConfig: config.SolanaConfig,
134134
antiAbuseOracles: config.AntiAbuseOracles,
135+
validators: config.Nodes,
135136
}
136137

137138
app.Use(recover.New(recover.Config{
@@ -271,6 +272,7 @@ type ApiServer struct {
271272
rewardAttester rewards.RewardAttester
272273
solanaConfig config.SolanaConfig
273274
antiAbuseOracles []string
275+
validators []config.Node
274276
}
275277

276278
func (app *ApiServer) home(c *fiber.Ctx) error {

api/spl/programs/reward_manager/EvaluateAttestations.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ func (inst *EvaluateAttestation) SetPayer(payer solana.PublicKey) *EvaluateAttes
7171
}
7272

7373
func (inst EvaluateAttestation) Build() *Instruction {
74-
authority, _, _ := deriveAuthority(ProgramID, inst.RewardManagerState)
75-
attestations, _, _ := deriveAttestations(ProgramID, authority, inst.DisbursementID)
74+
authority, _, _ := DeriveAuthorityAccount(ProgramID, inst.RewardManagerState)
75+
attestations, _, _ := DeriveAttestationsAccount(ProgramID, authority, inst.DisbursementID)
7676
disbursement, _, _ := deriveDisbursement(ProgramID, authority, inst.DisbursementID)
7777
antiAbuseOracle, _, _ := deriveSender(ProgramID, authority, inst.AntiAbuseOracleEthAddress)
7878

api/spl/programs/reward_manager/SubmitAttestation.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ func (inst *SubmitAttestation) SetPayer(payer solana.PublicKey) *SubmitAttestati
4444
}
4545

4646
func (inst SubmitAttestation) Build() *Instruction {
47-
authority, _, _ := deriveAuthority(ProgramID, inst.RewardManagerState)
47+
authority, _, _ := DeriveAuthorityAccount(ProgramID, inst.RewardManagerState)
4848
sender, _, _ := deriveSender(ProgramID, authority, inst.SenderEthAddress)
49-
attestations, _, _ := deriveAttestations(ProgramID, authority, inst.DisbursementID)
49+
attestations, _, _ := DeriveAttestationsAccount(ProgramID, authority, inst.DisbursementID)
5050

5151
inst.AccountMetaSlice = []*solana.AccountMeta{
5252
{

api/spl/programs/reward_manager/accounts.go

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package reward_manager
22

33
import (
4+
"encoding/binary"
45
"encoding/hex"
6+
"errors"
57
"strings"
68

9+
"github.com/AudiusProject/audiusd/pkg/rewards"
10+
bin "github.com/gagliardetto/binary"
711
"github.com/gagliardetto/solana-go"
812
)
913

@@ -14,7 +18,114 @@ type RewardManagerState struct {
1418
MinVotes uint8
1519
}
1620

17-
func deriveAuthority(programId solana.PublicKey, state solana.PublicKey) (solana.PublicKey, uint8, error) {
21+
type Attestation struct {
22+
SenderEthAddress string
23+
Claim rewards.RewardClaim
24+
OperatorEthAddress string
25+
}
26+
27+
func (data *Attestation) UnmarshalWithDecoder(decoder *bin.Decoder) error {
28+
senderBytes, err := decoder.ReadNBytes(20)
29+
if err != nil {
30+
return err
31+
}
32+
data.SenderEthAddress = "0x" + hex.EncodeToString(senderBytes)
33+
34+
recipientBytes, err := decoder.ReadNBytes(20)
35+
if err != nil {
36+
return err
37+
}
38+
data.Claim.RecipientEthAddress = "0x" + hex.EncodeToString(recipientBytes)
39+
40+
// Read separator _
41+
decoder.SkipBytes(uint(1))
42+
43+
amount, err := decoder.ReadUint64(binary.LittleEndian)
44+
if err != nil {
45+
return err
46+
}
47+
data.Claim.Amount = amount
48+
49+
// Read separator _
50+
decoder.SkipBytes(uint(1))
51+
52+
disbursementIdBytes := []byte{}
53+
bytesLeft := 32
54+
for {
55+
b, err := decoder.ReadByte()
56+
if err != nil {
57+
return err
58+
}
59+
60+
if b == 0 || b == []byte("_")[0] {
61+
break
62+
}
63+
bytesLeft = bytesLeft - 1
64+
disbursementIdBytes = append(disbursementIdBytes, b)
65+
}
66+
disbursementIdParts := strings.Split(string(disbursementIdBytes), ":")
67+
if len(disbursementIdParts) < 2 {
68+
return errors.New("invalid disbursement ID")
69+
}
70+
data.Claim.RewardID = disbursementIdParts[0]
71+
data.Claim.Specifier = strings.Join(disbursementIdParts[1:], ":")
72+
73+
oracleBytes, err := decoder.ReadNBytes(20)
74+
if err != nil {
75+
return err
76+
}
77+
data.Claim.AntiAbuseOracleEthAddress = "0x" + hex.EncodeToString(oracleBytes)
78+
if data.Claim.AntiAbuseOracleEthAddress == "0x0000000000000000000000000000000000000000" {
79+
data.Claim.AntiAbuseOracleEthAddress = ""
80+
}
81+
82+
// Skip unused bytes
83+
decoder.SkipBytes(uint(bytesLeft))
84+
85+
// claim padding
86+
decoder.SkipBytes(uint(45))
87+
88+
operatorBytes, err := decoder.ReadBytes(20)
89+
if err != nil {
90+
return err
91+
}
92+
data.OperatorEthAddress = "0x" + hex.EncodeToString(operatorBytes)
93+
94+
return nil
95+
}
96+
97+
type AttestationsAccountData struct {
98+
Version uint8
99+
RewardManagerState solana.PublicKey
100+
Count uint8
101+
Messages []Attestation
102+
}
103+
104+
func (data *AttestationsAccountData) UnmarshalWithDecoder(decoder *bin.Decoder) error {
105+
err := decoder.Decode(&data.Version)
106+
if err != nil {
107+
return err
108+
}
109+
err = decoder.Decode(&data.RewardManagerState)
110+
if err != nil {
111+
return err
112+
}
113+
err = decoder.Decode(&data.Count)
114+
if err != nil {
115+
return err
116+
}
117+
118+
data.Messages = make([]Attestation, data.Count)
119+
for i := range data.Count {
120+
err = decoder.Decode(&data.Messages[i])
121+
if err != nil {
122+
return err
123+
}
124+
}
125+
return nil
126+
}
127+
128+
func DeriveAuthorityAccount(programId solana.PublicKey, state solana.PublicKey) (solana.PublicKey, uint8, error) {
18129
seeds := make([][]byte, 1)
19130
seeds[0] = state.Bytes()[0:32]
20131
return solana.FindProgramAddress(seeds, programId)
@@ -34,7 +145,7 @@ func deriveSender(programId solana.PublicKey, authority solana.PublicKey, ethAdd
34145
return solana.FindProgramAddress([][]byte{authority.Bytes()[0:32], senderSeed}, programId)
35146
}
36147

37-
func deriveAttestations(programId solana.PublicKey, authority solana.PublicKey, disbursementId string) (solana.PublicKey, uint8, error) {
148+
func DeriveAttestationsAccount(programId solana.PublicKey, authority solana.PublicKey, disbursementId string) (solana.PublicKey, uint8, error) {
38149
attestationsSeed := make([]byte, len(AttestationsSeedPrefix)+len(disbursementId))
39150
copy(attestationsSeed, []byte(AttestationsSeedPrefix))
40151
copy(attestationsSeed[len([]byte(AttestationsSeedPrefix)):], disbursementId)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package reward_manager_test
2+
3+
import (
4+
"encoding/base64"
5+
"testing"
6+
7+
"bridgerton.audius.co/api/spl/programs/reward_manager"
8+
bin "github.com/gagliardetto/binary"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestDecodeAttestationsAccount(t *testing.T) {
13+
testData, err := base64.StdEncoding.DecodeString("AeeCH+of/iC5SPD28pk1Fv5CZ/fBO1jttO39bISSUz9sBAC2Ri6VXaWEG22eHiUpuDDwDzG/nxMmaYsG6TJgMoFyC0Aizx+D7iJfAOH1BQAAAABfYjo1ZjI1NjEyOjI5MzVkYWNiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkYulV2lhBttnh4lKbgw8A8xv/fJaRa9N6121O7dZTa4HClwbIBWnxMmaYsG6TJgMoFyC0Aizx+D7iJfAOH1BQAAAABfYjo1ZjI1NjEyOjI5MzVkYWNiXwC2Ri6VXaWEG22eHiUpuDDwDzG/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD3yWkWvTetdtTu3WU2uBwpcGyAVl6Yy+6qKs7ewIM6w9FjTip64PPCnxMmaYsG6TJgMoFyC0Aizx+D7iJfAOH1BQAAAABfYjo1ZjI1NjEyOjI5MzVkYWNiXwC2Ri6VXaWEG22eHiUpuDDwDzG/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABemMvuqirO3sCDOsPRY04qeuDzwo/PoQvTgIVwmH27Wx70q3RAD7/anxMmaYsG6TJgMoFyC0Aizx+D7iJfAOH1BQAAAABfYjo1ZjI1NjEyOjI5MzVkYWNiXwC2Ri6VXaWEG22eHiUpuDDwDzG/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACPz6EL04CFcJh9u1se9Kt0QA+/2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
14+
require.NoError(t, err)
15+
16+
result := reward_manager.AttestationsAccountData{}
17+
result.UnmarshalWithDecoder(bin.NewBinDecoder(testData))
18+
19+
require.Equal(t, uint8(1), result.Version)
20+
require.Equal(t, "GaiG9LDYHfZGqeNaoGRzFEnLiwUT7WiC6sA6FDJX9ZPq", result.RewardManagerState.String())
21+
require.Equal(t, uint8(4), result.Count)
22+
require.Len(t, result.Messages, 4)
23+
24+
// Message 1
25+
require.Equal(t, "0x00b6462e955da5841b6d9e1e2529b830f00f31bf", result.Messages[0].SenderEthAddress)
26+
require.Equal(t, uint64(100000000), result.Messages[0].Claim.Amount)
27+
require.Equal(t, "b", result.Messages[0].Claim.RewardID)
28+
require.Equal(t, "5f25612:2935dacb", result.Messages[0].Claim.Specifier)
29+
require.Equal(t, "0x9f1326698b06e932603281720b4022cf1f83ee22", result.Messages[0].Claim.RecipientEthAddress)
30+
require.Equal(t, "", result.Messages[0].Claim.AntiAbuseOracleEthAddress)
31+
require.Equal(t, "0x00b6462e955da5841b6d9e1e2529b830f00f31bf", result.Messages[0].OperatorEthAddress)
32+
33+
// Message 2
34+
require.Equal(t, "0xf7c96916bd37ad76d4eedd6536b81c29706c8056", result.Messages[1].SenderEthAddress)
35+
require.Equal(t, uint64(100000000), result.Messages[1].Claim.Amount)
36+
require.Equal(t, "b", result.Messages[1].Claim.RewardID)
37+
require.Equal(t, "5f25612:2935dacb", result.Messages[1].Claim.Specifier)
38+
require.Equal(t, "0x9f1326698b06e932603281720b4022cf1f83ee22", result.Messages[1].Claim.RecipientEthAddress)
39+
require.Equal(t, "0x00b6462e955da5841b6d9e1e2529b830f00f31bf", result.Messages[1].Claim.AntiAbuseOracleEthAddress)
40+
require.Equal(t, "0xf7c96916bd37ad76d4eedd6536b81c29706c8056", result.Messages[1].OperatorEthAddress)
41+
42+
// Message 3
43+
require.Equal(t, "0x5e98cbeeaa2acedec0833ac3d1634e2a7ae0f3c2", result.Messages[2].SenderEthAddress)
44+
require.Equal(t, uint64(100000000), result.Messages[2].Claim.Amount)
45+
require.Equal(t, "b", result.Messages[2].Claim.RewardID)
46+
require.Equal(t, "5f25612:2935dacb", result.Messages[2].Claim.Specifier)
47+
require.Equal(t, "0x9f1326698b06e932603281720b4022cf1f83ee22", result.Messages[2].Claim.RecipientEthAddress)
48+
require.Equal(t, "0x00b6462e955da5841b6d9e1e2529b830f00f31bf", result.Messages[2].Claim.AntiAbuseOracleEthAddress)
49+
require.Equal(t, "0x5e98cbeeaa2acedec0833ac3d1634e2a7ae0f3c2", result.Messages[2].OperatorEthAddress)
50+
51+
// Message 4
52+
require.Equal(t, "0x8fcfa10bd3808570987dbb5b1ef4ab74400fbfda", result.Messages[3].SenderEthAddress)
53+
require.Equal(t, uint64(100000000), result.Messages[3].Claim.Amount)
54+
require.Equal(t, "b", result.Messages[3].Claim.RewardID)
55+
require.Equal(t, "5f25612:2935dacb", result.Messages[3].Claim.Specifier)
56+
require.Equal(t, "0x9f1326698b06e932603281720b4022cf1f83ee22", result.Messages[3].Claim.RecipientEthAddress)
57+
require.Equal(t, "0x00b6462e955da5841b6d9e1e2529b830f00f31bf", result.Messages[3].Claim.AntiAbuseOracleEthAddress)
58+
require.Equal(t, "0x8fcfa10bd3808570987dbb5b1ef4ab74400fbfda", result.Messages[3].OperatorEthAddress)
59+
60+
}

0 commit comments

Comments
 (0)