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
97 changes: 96 additions & 1 deletion ledger/byron/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@
package byron

import (
"encoding/base64"
"encoding/json"
"io"
"os"
"slices"
"strconv"

"github.com/blinklabs-io/gouroboros/cbor"
"github.com/blinklabs-io/gouroboros/ledger/common"
)

type ByronGenesis struct {
Expand All @@ -28,7 +34,7 @@ type ByronGenesis struct {
StartTime int
BootStakeholders map[string]int
HeavyDelegation map[string]ByronGenesisHeavyDelegation
NonAvvmBalances any
NonAvvmBalances map[string]string
VssCerts map[string]ByronGenesisVssCert
}

Expand Down Expand Up @@ -81,6 +87,95 @@ type ByronGenesisVssCert struct {
VssKey string
}

func (g *ByronGenesis) GenesisUtxos() ([]common.Utxo, error) {
avvmUtxos, err := g.avvmUtxos()
if err != nil {
return nil, err
}
nonAvvmUtxos, err := g.nonAvvmUtxos()
if err != nil {
return nil, err
}
ret := slices.Concat(
avvmUtxos,
nonAvvmUtxos,
)
return ret, nil
}

func (g *ByronGenesis) avvmUtxos() ([]common.Utxo, error) {
var ret []common.Utxo
for pubkey, amount := range g.AvvmDistr {
// Build address from redeem pubkey
pubkeyBytes, err := base64.URLEncoding.DecodeString(pubkey)
if err != nil {
return nil, err
}
tmpAddr, err := common.NewByronAddressRedeem(
pubkeyBytes,
// XXX: do we need to specify the network ID?
common.ByronAddressAttributes{},
)
if err != nil {
return nil, err
}
tmpAmount, err := strconv.ParseUint(amount, 10, 64)
if err != nil {
return nil, err
}
addrBytes, err := cbor.Encode(tmpAddr)
if err != nil {
return nil, err
}
ret = append(
ret,
common.Utxo{
Id: ByronTransactionInput{
TxId: common.Blake2b256Hash(addrBytes),
OutputIndex: 0,
},
Output: ByronTransactionOutput{
OutputAddress: tmpAddr,
OutputAmount: tmpAmount,
},
},
)
}
return ret, nil
}

func (g *ByronGenesis) nonAvvmUtxos() ([]common.Utxo, error) {
var ret []common.Utxo
for address, amount := range g.NonAvvmBalances {
tmpAddr, err := common.NewAddress(address)
if err != nil {
return nil, err
}
tmpAmount, err := strconv.ParseUint(amount, 10, 64)
if err != nil {
return nil, err
}
addrBytes, err := cbor.Encode(tmpAddr)
if err != nil {
return nil, err
}
ret = append(
ret,
common.Utxo{
Id: ByronTransactionInput{
TxId: common.Blake2b256Hash(addrBytes),
OutputIndex: 0,
},
Output: ByronTransactionOutput{
OutputAddress: tmpAddr,
OutputAmount: tmpAmount,
},
},
)
}
return ret, nil
}

func NewByronGenesisFromReader(r io.Reader) (ByronGenesis, error) {
var ret ByronGenesis
dec := json.NewDecoder(r)
Expand Down
86 changes: 84 additions & 2 deletions ledger/byron/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package byron_test

import (
"encoding/json"
"reflect"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -97,7 +99,6 @@ const byronGenesisConfig = `
`

var expectedGenesisObj = byron.ByronGenesis{
// TODO
AvvmDistr: map[string]string{
"-0BJDi-gauylk4LptQTgjMeo7kY9lTCbZv12vwOSTZk=": "9999300000000",
"-0Np4pyTOWF26iXWVIvu6fhz9QupwWRS2hcCaOEYlw0=": "3760024000000",
Expand Down Expand Up @@ -188,7 +189,7 @@ var expectedGenesisObj = byron.ByronGenesis{
Omega: 0,
},
},
NonAvvmBalances: map[string]interface{}{},
NonAvvmBalances: map[string]string{},
VssCerts: map[string]byron.ByronGenesisVssCert{
"0efd6f3b2849d5baf25b3e2bf2d46f88427b4e455fc3dc43f57819c5": {
ExpiryEpoch: 5,
Expand Down Expand Up @@ -250,3 +251,84 @@ func TestGenesisFromJson(t *testing.T) {
)
}
}

func TestGenesisNonAvvmUtxos(t *testing.T) {
testAddr := "FHnt4NL7yPXvDWHa8bVs73UEUdJd64VxWXSFNqetECtYfTd9TtJguJ14Lu3feth"
testAmount := uint64(30_000_000_000_000_000)
expectedTxId := "4843cf2e582b2f9ce37600e5ab4cc678991f988f8780fed05407f9537f7712bd"
// Generate genesis config JSON
tmpGenesisData := map[string]any{
"nonAvvmBalances": map[string]string{
testAddr: strconv.FormatUint(testAmount, 10),
},
}
tmpGenesisJson, err := json.Marshal(tmpGenesisData)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// Parse genesis config JSON
tmpGenesis, err := byron.NewByronGenesisFromReader(
strings.NewReader(string(tmpGenesisJson)),
)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tmpGenesisUtxos, err := tmpGenesis.GenesisUtxos()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if len(tmpGenesisUtxos) != 1 {
t.Fatalf("did not get expected count of genesis UTxOs")
}
tmpUtxo := tmpGenesisUtxos[0]
if tmpUtxo.Id.Id().String() != expectedTxId {
t.Fatalf("did not get expected TxID: got %s, wanted %s", tmpUtxo.Id.Id().String(), expectedTxId)
}
if tmpUtxo.Output.Address().String() != testAddr {
t.Fatalf("did not get expected address: got %s, wanted %s", tmpUtxo.Output.Address().String(), testAddr)
}
if tmpUtxo.Output.Amount() != testAmount {
t.Fatalf("did not get expected amount: got %d, wanted %d", tmpUtxo.Output.Amount(), testAmount)
}
}

func TestGenesisAvvmUtxos(t *testing.T) {
testPubkey := "URVk8FxX6Ik9z-Cub09oOxMkp6FwNq27kJUXbjJnfsQ="
testAmount := uint64(2463071701000000)
expectedTxId := "0ae3da29711600e94a33fb7441d2e76876a9a1e98b5ebdefbf2e3bc535617616"
expectedAddr := "Ae2tdPwUPEZKQuZh2UndEoTKEakMYHGNjJVYmNZgJk2qqgHouxDsA5oT83n"
// Generate genesis config JSON
tmpGenesisData := map[string]any{
"avvmDistr": map[string]string{
testPubkey: strconv.FormatUint(testAmount, 10),
},
}
tmpGenesisJson, err := json.Marshal(tmpGenesisData)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// Parse genesis config JSON
tmpGenesis, err := byron.NewByronGenesisFromReader(
strings.NewReader(string(tmpGenesisJson)),
)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tmpGenesisUtxos, err := tmpGenesis.GenesisUtxos()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if len(tmpGenesisUtxos) != 1 {
t.Fatalf("did not get expected count of genesis UTxOs")
}
tmpUtxo := tmpGenesisUtxos[0]
if tmpUtxo.Id.Id().String() != expectedTxId {
t.Fatalf("did not get expected TxID: got %s, wanted %s", tmpUtxo.Id.Id().String(), expectedTxId)
}
if tmpUtxo.Output.Address().String() != expectedAddr {
t.Fatalf("did not get expected address: got %s, wanted %s", tmpUtxo.Output.Address().String(), expectedAddr)
}
if tmpUtxo.Output.Amount() != testAmount {
t.Fatalf("did not get expected amount: got %d, wanted %d", tmpUtxo.Output.Amount(), testAmount)
}
}
33 changes: 33 additions & 0 deletions ledger/common/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/blinklabs-io/gouroboros/base58"
"github.com/blinklabs-io/gouroboros/bech32"
"github.com/blinklabs-io/gouroboros/cbor"
"golang.org/x/crypto/sha3"
)

const (
Expand Down Expand Up @@ -135,6 +136,38 @@ func NewByronAddressFromParts(
}, nil
}

func NewByronAddressRedeem(
pubkey []byte,
attr ByronAddressAttributes,
) (Address, error) {
if len(pubkey) != 32 {
return Address{}, fmt.Errorf(
"invalid redeem pubkey length: %d",
len(pubkey),
)
}
addrRoot := []any{
ByronAddressTypeRedeem,
[]any{
ByronAddressTypeRedeem,
pubkey,
},
attr,
}
addrRootBytes, err := cbor.Encode(addrRoot)
if err != nil {
return Address{}, err
}
sha3Sum := sha3.Sum256(addrRootBytes)
addrHash := Blake2b224Hash(sha3Sum[:])
return Address{
addressType: AddressTypeByron,
paymentAddress: addrHash.Bytes(),
byronAddressType: ByronAddressTypeRedeem,
byronAddressAttr: attr,
}, nil
}

func (a *Address) populateFromBytes(data []byte) error {
// Extract header info
header := data[0]
Expand Down
Loading