bindbox-game/internal/service/activity/activity_commitment_service.go

108 lines
3.5 KiB
Go

package activity
import (
"bindbox-game/internal/repository/mysql"
"bindbox-game/internal/repository/mysql/dao"
"context"
"crypto/rand"
"crypto/sha256"
"errors"
)
type ActivityCommitmentService struct {
read *dao.Query
write *dao.Query
repo mysql.Repo
}
func NewActivityCommitmentService(read *dao.Query, write *dao.Query, repo mysql.Repo) *ActivityCommitmentService {
return &ActivityCommitmentService{read: read, write: write, repo: repo}
}
func (s *ActivityCommitmentService) Generate(ctx context.Context, activityID int64) (int32, error) {
act, err := s.read.Activities.WithContext(ctx).Where(s.read.Activities.ID.Eq(activityID)).First()
if err != nil || act == nil {
return 0, errors.New("activity not found")
}
var cur int32
_ = s.repo.GetDbR().Raw("SELECT IFNULL(commitment_state_version,0) FROM activities WHERE id=?", activityID).Scan(&cur)
seed := make([]byte, 32)
_, _ = rand.Read(seed)
seedHash := sha256.Sum256(seed)
// compute items_root by aggregating all issues' reward slots
issueIDs := make([]int64, 0)
issues, _ := s.read.ActivityIssues.WithContext(ctx).ReadDB().Where(s.read.ActivityIssues.ActivityID.Eq(activityID)).Find()
for _, is := range issues {
issueIDs = append(issueIDs, is.ID)
}
var itemsRoot []byte
if len(issueIDs) > 0 {
// fetch rewards per issue and build slots
slots := make([]int64, 0)
for _, iid := range issueIDs {
rs, _ := s.read.ActivityRewardSettings.WithContext(ctx).ReadDB().Where(s.read.ActivityRewardSettings.IssueID.Eq(iid)).Order(
s.read.ActivityRewardSettings.IsBoss.Desc(),
s.read.ActivityRewardSettings.Level.Asc(),
s.read.ActivityRewardSettings.Sort.Asc(),
).Find()
for _, r := range rs {
for i := int64(0); i < r.OriginalQty; i++ {
slots = append(slots, r.ID)
}
}
}
if len(slots) > 0 {
// naive JSON encoding to compute root
// Note: avoid importing encoding/json at top, compute hash over formatted bytes
// but simpler: use []byte with little-endian concatenation
h := sha256.New()
for _, v := range slots {
var buf [8]byte
// big endian
buf[0] = byte(v >> 56)
buf[1] = byte(v >> 48)
buf[2] = byte(v >> 40)
buf[3] = byte(v >> 32)
buf[4] = byte(v >> 24)
buf[5] = byte(v >> 16)
buf[6] = byte(v >> 8)
buf[7] = byte(v)
h.Write(buf[:])
}
itemsRoot = h.Sum(nil)
}
}
ver := cur + 1
_, err = s.write.Activities.WithContext(ctx).Where(s.write.Activities.ID.Eq(act.ID)).Updates(map[string]any{
"commitment_algo": "commit-v1",
"commitment_seed_master": seed,
"commitment_seed_hash": seedHash[:],
"commitment_state_version": ver,
"commitment_items_root": itemsRoot,
})
if err != nil {
return 0, err
}
return ver, nil
}
type ActivityCommitmentSummary struct {
SeedVersion int32
Algo string
HasSeed bool
ItemsRoot []byte
}
func (s *ActivityCommitmentService) Summary(ctx context.Context, activityID int64) (ActivityCommitmentSummary, error) {
var ver int32
var algo string
var hasSeed bool
// read version
_ = s.repo.GetDbR().Raw("SELECT IFNULL(commitment_state_version,0) FROM activities WHERE id=?", activityID).Scan(&ver)
_ = s.repo.GetDbR().Raw("SELECT commitment_algo FROM activities WHERE id=?", activityID).Scan(&algo)
var seedLen *int
_ = s.repo.GetDbR().Raw("SELECT LENGTH(commitment_seed_master) FROM activities WHERE id=?", activityID).Scan(&seedLen)
hasSeed = seedLen != nil && *seedLen > 0
return ActivityCommitmentSummary{SeedVersion: ver, Algo: algo, HasSeed: hasSeed, ItemsRoot: nil}, nil
}