diff --git a/internal/shared/utils/datagen/datagen.go b/internal/shared/utils/datagen/datagen.go new file mode 100644 index 0000000..5b04488 --- /dev/null +++ b/internal/shared/utils/datagen/datagen.go @@ -0,0 +1,215 @@ +package datagen + +import ( + "bytes" + "encoding/hex" + "fmt" + "log" + "math/rand" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +func GenRandomByteArray(r *rand.Rand, length uint64) []byte { + newHeaderBytes := make([]byte, length) + r.Read(newHeaderBytes) + return newHeaderBytes +} + +func RandomPk() (string, error) { + fpPirvKey, err := btcec.NewPrivateKey() + if err != nil { + return "", err + } + fpPk := fpPirvKey.PubKey() + return hex.EncodeToString(schnorr.SerializePubKey(fpPk)), nil +} + +func GeneratePks(numOfKeys int) []string { + var pks []string + for i := 0; i < numOfKeys; i++ { + k, err := RandomPk() + if err != nil { + log.Fatalf("Failed to generate random pk: %v", err) + } + pks = append(pks, k) + } + return pks +} + +// RandomPostiveFloat64 generates a random float64 value greater than 0. +func RandomPostiveFloat64(r *rand.Rand) float64 { + for { + f := r.Float64() // Generate a random float64 + if f > 0 { + return f + } + // If f is 0 (extremely rare), regenerate + } +} + +// RandomPositiveInt generates a random positive integer from 1 to max. +func RandomPositiveInt(r *rand.Rand, max int) int { + // Generate a random number from 1 to max (inclusive) + return r.Intn(max) + 1 +} + +// RandomString generates a random alphanumeric string of length n. +func RandomString(r *rand.Rand, n int) string { + result := make([]byte, n) + letterLen := len(letters) + for i := range result { + num := r.Int() % letterLen + result[i] = letters[num] + } + return string(result) +} + +// RandomAmount generates a random BTC amount from 0.1 to 10000 +// the returned value is in satoshis +func RandomAmount(r *rand.Rand) int64 { + // Generate a random value range from 0.1 to 10000 BTC + randomBTC := r.Float64()*(9999.9-0.1) + 0.1 + // convert to satoshi + return int64(randomBTC*1e8) + 1 +} + +// GenerateRandomTx generates a random transaction with random values for each field. +func GenerateRandomTx( + r *rand.Rand, + options *struct{ DisableRbf bool }, +) (*wire.MsgTx, string, error) { + sequence := r.Uint32() + if options != nil && options.DisableRbf { + sequence = wire.MaxTxInSequenceNum + } + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.HashH(GenRandomByteArray(r, 10)), + Index: r.Uint32(), + }, + SignatureScript: []byte{}, + Sequence: sequence, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: int64(r.Int31()), + PkScript: GenRandomByteArray(r, 80), + }, + }, + LockTime: 0, + } + var buf bytes.Buffer + if err := tx.Serialize(&buf); err != nil { + return nil, "", err + } + txHex := hex.EncodeToString(buf.Bytes()) + + return tx, txHex, nil +} + +// GenerateRandomTxWithOutput generates a random transaction with random values +// for each field. +func RandomBytes(r *rand.Rand, n uint64) ([]byte, string) { + randomBytes := GenRandomByteArray(r, n) + return randomBytes, hex.EncodeToString(randomBytes) +} + +// GenerateRandomTimestamp generates a random timestamp before the specified timestamp. +// If beforeTimestamp is 0, then the current time is used. +func GenerateRandomTimestamp(afterTimestamp, beforeTimestamp int64) int64 { + timeNow := time.Now().Unix() + if beforeTimestamp == 0 && afterTimestamp == 0 { + return timeNow + } + if beforeTimestamp == 0 { + return afterTimestamp + rand.Int63n(timeNow-afterTimestamp) + } else if afterTimestamp == 0 { + // Generate a reasonable timestamp between 1 second to 6 months in the past + sixMonthsInSeconds := int64(6 * 30 * 24 * 60 * 60) + return beforeTimestamp - rand.Int63n(sixMonthsInSeconds) + } + return afterTimestamp + rand.Int63n(beforeTimestamp-afterTimestamp) +} + +// GenerateRandomFinalityProviderDetail generates a random number of finality providers +func GenerateRandomFinalityProviderDetail(r *rand.Rand, numOfFps uint64) []types.FinalityProviderDetails { + var finalityProviders []types.FinalityProviderDetails + + for i := uint64(0); i < numOfFps; i++ { + fpPkInHex, err := RandomPk() + if err != nil { + log.Fatalf("failed to generate random public key: %v", err) + } + + randomStr := RandomString(r, 10) + finalityProviders = append(finalityProviders, types.FinalityProviderDetails{ + Description: types.FinalityProviderDescription{ + Moniker: "Moniker" + randomStr, + Identity: "Identity" + randomStr, + Website: "Website" + randomStr, + SecurityContact: "SecurityContact" + randomStr, + Details: "Details" + randomStr, + }, + Commission: fmt.Sprintf("%f", RandomPostiveFloat64(r)), + BtcPk: fpPkInHex, + }) + } + return finalityProviders +} + +func RandomFinalityProviderState(r *rand.Rand) types.FinalityProviderState { + states := []types.FinalityProviderState{types.FinalityProviderStateActive, types.FinalityProviderStateStandby} + return states[r.Intn(len(states))] +} + +func GenerateRandomBabylonParams(r *rand.Rand) types.BabylonParams { + return types.BabylonParams{ + Version: r.Intn(10), + CovenantPKs: GeneratePks(r.Intn(10)), + CovenantQuorum: r.Intn(10), + MaxStakingAmount: int64(r.Intn(1000000000000000000)), + MinStakingAmount: int64(r.Intn(1000000000000000000)), + MaxStakingTime: int64(r.Intn(1000000000000000000)), + MinStakingTime: int64(r.Intn(1000000000000000000)), + SlashingPKScript: RandomString(r, 10), + MinSlashingTxFee: int64(r.Intn(1000000000000000000)), + SlashingRate: RandomPostiveFloat64(r), + MinUnbondingTime: int64(r.Intn(1000000000000000000)), + UnbondingFee: int64(r.Intn(1000000000000000000)), + MinCommissionRate: RandomPostiveFloat64(r), + MaxActiveFinalityProviders: r.Intn(10), + DelegationCreationBaseGasFee: int64(r.Intn(1000000000000000000)), + } +} + +func GenerateRandomBTCParams(r *rand.Rand) types.BTCParams { + return types.BTCParams{ + Version: r.Intn(10), + BTCConfirmationDepth: r.Intn(10), + } +} + +func RandomDelegationState(r *rand.Rand) types.DelegationState { + states := []types.DelegationState{types.Active, types.UnbondingRequested, types.Unbonding, types.Unbonded, types.Withdrawn} + return states[r.Intn(len(states))] +} + +func RandomTransactionInfo(r *rand.Rand) types.TransactionInfo { + _, txHex, _ := GenerateRandomTx(r, nil) + return types.TransactionInfo{ + TxHex: txHex, + OutputIndex: r.Intn(100), + } +} diff --git a/internal/v2/service/finality_provider.go b/internal/v2/service/finality_provider.go index 365ecca..729593c 100644 --- a/internal/v2/service/finality_provider.go +++ b/internal/v2/service/finality_provider.go @@ -2,11 +2,8 @@ package v2service import ( "context" - "math/rand" - "time" "github.com/babylonlabs-io/staking-api-service/internal/shared/types" - "github.com/babylonlabs-io/staking-api-service/tests/testutils" ) type FinalityProviderPublic struct { @@ -26,23 +23,24 @@ type FinalityProvidersPublic struct { } func (s *V2Service) GetFinalityProviders(ctx context.Context, paginationKey string) ([]FinalityProviderPublic, string, *types.Error) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - // random number of providers between 1 and 10 - numProviders := testutils.RandomPositiveInt(r, 10) - providers := testutils.GenerateRandomFinalityProviderDetail(r, uint64(numProviders)) - publicProviders := make([]FinalityProviderPublic, len(providers)) - for i, provider := range providers { - publicProviders[i] = FinalityProviderPublic{ - BtcPK: testutils.GeneratePks(1)[0], - State: testutils.RandomFinalityProviderState(r), - Description: provider.Description, - Commission: provider.Commission, - ActiveTVL: int64(testutils.RandomPositiveInt(r, 1000000000000000000)), - TotalTVL: int64(testutils.RandomPositiveInt(r, 1000000000000000000)), - ActiveDelegations: int64(testutils.RandomPositiveInt(r, 100)), - TotalDelegations: int64(testutils.RandomPositiveInt(r, 100)), - } - } + // r := rand.New(rand.NewSource(time.Now().UnixNano())) + // // random number of providers between 1 and 10 + // numProviders := datagen.RandomPositiveInt(r, 10) + // providers := datagen.GenerateRandomFinalityProviderDetail(r, uint64(numProviders)) + // publicProviders := make([]FinalityProviderPublic, len(providers)) + // for i, provider := range providers { + // publicProviders[i] = FinalityProviderPublic{ + // BtcPK: datagen.GeneratePks(1)[0], + // State: datagen.RandomFinalityProviderState(r), + // Description: provider.Description, + // Commission: provider.Commission, + // ActiveTVL: int64(datagen.RandomPositiveInt(r, 1000000000000000000)), + // TotalTVL: int64(datagen.RandomPositiveInt(r, 1000000000000000000)), + // ActiveDelegations: int64(datagen.RandomPositiveInt(r, 100)), + // TotalDelegations: int64(datagen.RandomPositiveInt(r, 100)), + // } + // } - return publicProviders, "", nil + // return publicProviders, "", nil + return nil, "", nil } diff --git a/internal/v2/service/global_params.go b/internal/v2/service/global_params.go index 2231cb3..96954f3 100644 --- a/internal/v2/service/global_params.go +++ b/internal/v2/service/global_params.go @@ -6,7 +6,7 @@ import ( "time" "github.com/babylonlabs-io/staking-api-service/internal/shared/types" - "github.com/babylonlabs-io/staking-api-service/tests/testutils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils/datagen" ) type GlobalParamsPublic struct { @@ -16,8 +16,8 @@ type GlobalParamsPublic struct { func (s *V2Service) GetGlobalParams(ctx context.Context) (GlobalParamsPublic, *types.Error) { r := rand.New(rand.NewSource(time.Now().UnixNano())) - babylonParams := testutils.GenerateRandomBabylonParams(r) - btcParams := testutils.GenerateRandomBTCParams(r) + babylonParams := datagen.GenerateRandomBabylonParams(r) + btcParams := datagen.GenerateRandomBTCParams(r) return GlobalParamsPublic{ Babylon: []types.BabylonParams{babylonParams}, BTC: []types.BTCParams{btcParams}, diff --git a/internal/v2/service/staker.go b/internal/v2/service/staker.go index 11151e7..1700a76 100644 --- a/internal/v2/service/staker.go +++ b/internal/v2/service/staker.go @@ -6,7 +6,7 @@ import ( "time" "github.com/babylonlabs-io/staking-api-service/internal/shared/types" - "github.com/babylonlabs-io/staking-api-service/tests/testutils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils/datagen" ) type StakerDelegationPublic struct { @@ -33,23 +33,23 @@ type StakerStatsPublic struct { func (s *V2Service) GetStakerDelegations(ctx context.Context, paginationKey string) ([]StakerDelegationPublic, string, *types.Error) { r := rand.New(rand.NewSource(time.Now().UnixNano())) // random positive int - numStakerDelegations := testutils.RandomPositiveInt(r, 10) + numStakerDelegations := datagen.RandomPositiveInt(r, 10) stakerDelegationPublics := []StakerDelegationPublic{} for i := 0; i < numStakerDelegations; i++ { - _, stakingTxHash, _ := testutils.GenerateRandomTx(r, nil) - stakerPkHex, _ := testutils.RandomPk() - fpPkHex, _ := testutils.RandomPk() + _, stakingTxHash, _ := datagen.GenerateRandomTx(r, nil) + stakerPkHex, _ := datagen.RandomPk() + fpPkHex, _ := datagen.RandomPk() stakerDelegation := &StakerDelegationPublic{ StakingTxHashHex: stakingTxHash, StakerPKHex: stakerPkHex, FinalityProviderPKHex: fpPkHex, - StakingStartHeight: int64(testutils.RandomPositiveInt(r, 1000000)), - UnbondingStartHeight: int64(testutils.RandomPositiveInt(r, 1000000)), - Timelock: int64(testutils.RandomPositiveInt(r, 1000000)), - StakingValue: testutils.RandomAmount(r), + StakingStartHeight: int64(datagen.RandomPositiveInt(r, 1000000)), + UnbondingStartHeight: int64(datagen.RandomPositiveInt(r, 1000000)), + Timelock: int64(datagen.RandomPositiveInt(r, 1000000)), + StakingValue: datagen.RandomAmount(r), State: types.Active.ToString(), - StakingTx: testutils.RandomTransactionInfo(r), - UnbondingTx: testutils.RandomTransactionInfo(r), + StakingTx: datagen.RandomTransactionInfo(r), + UnbondingTx: datagen.RandomTransactionInfo(r), } stakerDelegationPublics = append(stakerDelegationPublics, *stakerDelegation) } @@ -60,10 +60,10 @@ func (s *V2Service) GetStakerStats(ctx context.Context, stakerPKHex string) (Sta r := rand.New(rand.NewSource(time.Now().UnixNano())) stakerStats := StakerStatsPublic{ StakerPKHex: stakerPKHex, - ActiveTVL: int64(testutils.RandomPositiveInt(r, 1000000)), - TotalTVL: int64(testutils.RandomPositiveInt(r, 1000000)), - ActiveDelegations: int64(testutils.RandomPositiveInt(r, 100)), - TotalDelegations: int64(testutils.RandomPositiveInt(r, 100)), + ActiveTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + TotalTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + ActiveDelegations: int64(datagen.RandomPositiveInt(r, 100)), + TotalDelegations: int64(datagen.RandomPositiveInt(r, 100)), } return stakerStats, nil } diff --git a/internal/v2/service/stats.go b/internal/v2/service/stats.go index f14d4d8..62175b5 100644 --- a/internal/v2/service/stats.go +++ b/internal/v2/service/stats.go @@ -6,7 +6,7 @@ import ( "time" "github.com/babylonlabs-io/staking-api-service/internal/shared/types" - "github.com/babylonlabs-io/staking-api-service/tests/testutils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils/datagen" ) type OverallStatsPublic struct { @@ -23,14 +23,14 @@ type OverallStatsPublic struct { func (s *V2Service) GetOverallStats(ctx context.Context) (OverallStatsPublic, *types.Error) { r := rand.New(rand.NewSource(time.Now().UnixNano())) overallStats := OverallStatsPublic{ - ActiveTVL: int64(testutils.RandomPositiveInt(r, 1000000)), - TotalTVL: int64(testutils.RandomPositiveInt(r, 1000000)), - ActiveDelegations: int64(testutils.RandomPositiveInt(r, 100)), - TotalDelegations: int64(testutils.RandomPositiveInt(r, 100)), - ActiveStakers: int64(testutils.RandomPositiveInt(r, 100)), - TotalStakers: int64(testutils.RandomPositiveInt(r, 100)), - ActiveFinalityProviders: int64(testutils.RandomPositiveInt(r, 100)), - TotalFinalityProviders: int64(testutils.RandomPositiveInt(r, 100)), + ActiveTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + TotalTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + ActiveDelegations: int64(datagen.RandomPositiveInt(r, 100)), + TotalDelegations: int64(datagen.RandomPositiveInt(r, 100)), + ActiveStakers: int64(datagen.RandomPositiveInt(r, 100)), + TotalStakers: int64(datagen.RandomPositiveInt(r, 100)), + ActiveFinalityProviders: int64(datagen.RandomPositiveInt(r, 100)), + TotalFinalityProviders: int64(datagen.RandomPositiveInt(r, 100)), } return overallStats, nil }