Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
116 changes: 109 additions & 7 deletions ledger/shelley/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package shelley
import (
"encoding/hex"
"encoding/json"
"errors"
"io"
"math/big"
"os"
Expand All @@ -42,7 +43,40 @@ type ShelleyGenesis struct {
ProtocolParameters ShelleyGenesisProtocolParams `json:"protocolParams"`
GenDelegs map[string]map[string]string `json:"genDelegs"`
InitialFunds map[string]uint64 `json:"initialFunds"`
Staking any `json:"staking"`
Staking GenesisStaking `json:"staking"`
}

type GenesisStaking struct {
Pools map[string]GenesisPool `json:"pools"`
Stake map[string]string `json:"stake"`
}

type GenesisPool struct {
Cost int64 `json:"cost"`
Margin float64 `json:"margin"`
Metadata interface{} `json:"metadata"`
Owners []string `json:"owners"`
Pledge int64 `json:"pledge"`
PublicKey string `json:"publicKey"`
Relays []GenesisRelay `json:"relays"`
RewardAccount GenesisReward `json:"rewardAccount"`
Vrf string `json:"vrf"`
}

type GenesisRelay struct {
SingleHostName *SingleHostName `json:"single host name,omitempty"`
}

type SingleHostName struct {
DNSName string `json:"dnsName"`
Port int `json:"port"`
}

type GenesisReward struct {
Credential struct {
KeyHash string `json:"key hash"`
} `json:"credential"`
Network string `json:"network"`
}

func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) {
Expand All @@ -65,13 +99,52 @@ func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) {
cbor.NewByteString(vrfBytes),
}
}
staking := []any{}
if g.Staking == nil {
staking = []any{
map[any]any{},
map[any]any{},

// Convert pools to CBOR format
cborPools := make(map[cbor.ByteString]any)
for poolId, pool := range g.Staking.Pools {
poolIdBytes, err := hex.DecodeString(poolId)
if err != nil {
return nil, err
}
vrfBytes, err := hex.DecodeString(pool.Vrf)
if err != nil {
return nil, err
}
rewardAccountBytes, err := hex.DecodeString(pool.RewardAccount.Credential.KeyHash)
if err != nil {
return nil, err
}
cborPools[cbor.NewByteString(poolIdBytes)] = []any{
pool.Cost,
pool.Margin,
pool.Pledge,
pool.PublicKey,
[]any{
[]byte{0},
rewardAccountBytes,
},
pool.Owners,
pool.Relays,
vrfBytes,
pool.Metadata,
}
}

// Convert stake to CBOR format
cborStake := make(map[cbor.ByteString]cbor.ByteString)
for stakeAddr, poolId := range g.Staking.Stake {
stakeAddrBytes, err := hex.DecodeString(stakeAddr)
if err != nil {
return nil, err
}
poolIdBytes, err := hex.DecodeString(poolId)
if err != nil {
return nil, err
}
cborStake[cbor.NewByteString(stakeAddrBytes)] = cbor.NewByteString(poolIdBytes)
}

slotLengthMs := &big.Rat{}
tmpData := []any{
[]any{
Expand All @@ -95,7 +168,10 @@ func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) {
g.ProtocolParameters,
genDelegs,
g.InitialFunds,
staking,
[]any{
cborPools,
cborStake,
},
}
return cbor.Encode(tmpData)
}
Expand Down Expand Up @@ -128,6 +204,32 @@ func (g *ShelleyGenesis) GenesisUtxos() ([]common.Utxo, error) {
return ret, nil
}

// GetInitialPools returns all initial stake pools with their delegators
func (g *ShelleyGenesis) GetInitialPools() (map[string]GenesisPool, map[string][]string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Drop the Get prefix on the function. It's not idiomatic Go

Copy link
Contributor

Choose a reason for hiding this comment

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

We should return native ledger types here like we do above for GenesisUtxos(). At minimum, we should use common.Address where appropriate.

poolStake := make(map[string][]string)
for stakeAddr, poolId := range g.Staking.Stake {
poolStake[poolId] = append(poolStake[poolId], stakeAddr)
}
return g.Staking.Pools, poolStake, nil
}

// GetPoolById returns a specific pool by its ID along with its delegators
func (g *ShelleyGenesis) GetPoolById(poolId string) (*GenesisPool, []string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Drop the Get prefix on the function. It's not idiomatic Go

Copy link
Contributor

Choose a reason for hiding this comment

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

This should return []common.Address instead of []string

pool, exists := g.Staking.Pools[poolId]
if !exists {
return nil, nil, errors.New("pool not found")
}

var delegators []string
for stakeAddr, pId := range g.Staking.Stake {
if pId == poolId {
delegators = append(delegators, stakeAddr)
}
}

return &pool, delegators, nil
}

type ShelleyGenesisProtocolParams struct {
cbor.StructAsArray
MinFeeA uint `json:"minFeeA"`
Expand Down
165 changes: 165 additions & 0 deletions ledger/shelley/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,168 @@ func TestGenesisUtxos(t *testing.T) {
)
}
}

const shelleyGenesisWithStaking = `
{
"activeSlotsCoeff": 0.05,
"protocolParams": {
"protocolVersion": {
"minor": 0,
"major": 2
},
"decentralisationParam": 1,
"eMax": 18,
"extraEntropy": {
"tag": "NeutralNonce"
},
"maxTxSize": 16384,
"maxBlockBodySize": 65536,
"maxBlockHeaderSize": 1100,
"minFeeA": 44,
"minFeeB": 155381,
"minUTxOValue": 1000000,
"poolDeposit": 500000000,
"minPoolCost": 340000000,
"keyDeposit": 2000000,
"nOpt": 150,
"rho": 0.003,
"tau": 0.20,
"a0": 0.3
},
"genDelegs": {},
"updateQuorum": 5,
"networkId": "Testnet",
"initialFunds": {},
"maxLovelaceSupply": 45000000000000000,
"networkMagic": 764824073,
"epochLength": 432000,
"systemStart": "2017-09-23T21:44:51Z",
"slotsPerKESPeriod": 129600,
"slotLength": 1,
"maxKESEvolutions": 62,
"securityParam": 2160,
"staking": {
"pools": {
"0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195": {
"cost": 340000000,
"margin": 0.0,
"metadata": null,
"owners": [],
"pledge": 0,
"publicKey": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195",
"relays": [
{
"single host name": {
"dnsName": "p4.example",
"port": 3001
}
}
],
"rewardAccount": {
"credential": {
"key hash": "6079cde665c2035b8d9ac8929307bdd7f20a51e678e9d4a5e39ace3a"
},
"network": "Testnet"
},
"vrf": "eb53a17fbad9b7ea0bcf1e1ea89355305600d593b426dfc3084a924d8877d47e"
}
},
"stake": {
"24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195"
}
}
}
`

func TestGenesisStaking(t *testing.T) {
tmpGenesis, err := shelley.NewShelleyGenesisFromReader(
strings.NewReader(shelleyGenesisWithStaking),
)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

// Test GetInitialPools
pools, delegators, err := tmpGenesis.GetInitialPools()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

expectedPoolId := "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195"
if len(pools) != 1 {
t.Fatalf("expected 1 pool, got %d", len(pools))
}

pool, exists := pools[expectedPoolId]
if !exists {
t.Fatalf("expected pool with ID %s not found", expectedPoolId)
}

// Verify pool details
if pool.Cost != 340000000 {
t.Errorf("expected pool cost 340000000, got %d", pool.Cost)
}
if pool.Margin != 0.0 {
t.Errorf("expected pool margin 0.0, got %f", pool.Margin)
}
if pool.Pledge != 0 {
t.Errorf("expected pool pledge 0, got %d", pool.Pledge)
}
if pool.PublicKey != expectedPoolId {
t.Errorf("expected pool public key %s, got %s", expectedPoolId, pool.PublicKey)
}
if pool.Vrf != "eb53a17fbad9b7ea0bcf1e1ea89355305600d593b426dfc3084a924d8877d47e" {
t.Errorf("unexpected pool VRF key")
}

// Verify delegators
expectedStakeAddr := "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36"
if len(delegators[expectedPoolId]) != 1 {
t.Fatalf("expected 1 delegator for pool, got %d", len(delegators[expectedPoolId]))
}
if delegators[expectedPoolId][0] != expectedStakeAddr {
t.Errorf("expected stake address %s, got %s", expectedStakeAddr, delegators[expectedPoolId][0])
}

// Test GetPoolById
poolById, delegatorsById, err := tmpGenesis.GetPoolById(expectedPoolId)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

if poolById.PublicKey != expectedPoolId {
t.Errorf("GetPoolById: expected pool public key %s, got %s", expectedPoolId, poolById.PublicKey)
}

if len(delegatorsById) != 1 || delegatorsById[0] != expectedStakeAddr {
t.Errorf("GetPoolById: unexpected delegators")
}

// Test non-existent pool
_, _, err = tmpGenesis.GetPoolById("nonexistentpoolid")
if err == nil {
t.Error("expected error for non-existent pool, got nil")
} else if err.Error() != "pool not found" {
t.Errorf("expected 'pool not found' error, got: %v", err)
}
}

func TestGenesisStakingCBOR(t *testing.T) {
tmpGenesis, err := shelley.NewShelleyGenesisFromReader(
strings.NewReader(shelleyGenesisWithStaking),
)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

// Test CBOR marshaling
cborData, err := tmpGenesis.MarshalCBOR()
if err != nil {
t.Fatalf("unexpected error during CBOR marshaling: %s", err)
}

if len(cborData) == 0 {
t.Error("expected non-empty CBOR data, got empty")
}

}
Loading