Skip to content

Commit 474274b

Browse files
authored
feat: create genesis UTxOs from Byron genesis config (#826)
1 parent 6f7e55a commit 474274b

File tree

3 files changed

+213
-3
lines changed

3 files changed

+213
-3
lines changed

ledger/byron/genesis.go

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
package byron
1616

1717
import (
18+
"encoding/base64"
1819
"encoding/json"
1920
"io"
2021
"os"
22+
"slices"
23+
"strconv"
24+
25+
"github.com/blinklabs-io/gouroboros/cbor"
26+
"github.com/blinklabs-io/gouroboros/ledger/common"
2127
)
2228

2329
type ByronGenesis struct {
@@ -28,7 +34,7 @@ type ByronGenesis struct {
2834
StartTime int
2935
BootStakeholders map[string]int
3036
HeavyDelegation map[string]ByronGenesisHeavyDelegation
31-
NonAvvmBalances any
37+
NonAvvmBalances map[string]string
3238
VssCerts map[string]ByronGenesisVssCert
3339
}
3440

@@ -81,6 +87,95 @@ type ByronGenesisVssCert struct {
8187
VssKey string
8288
}
8389

90+
func (g *ByronGenesis) GenesisUtxos() ([]common.Utxo, error) {
91+
avvmUtxos, err := g.avvmUtxos()
92+
if err != nil {
93+
return nil, err
94+
}
95+
nonAvvmUtxos, err := g.nonAvvmUtxos()
96+
if err != nil {
97+
return nil, err
98+
}
99+
ret := slices.Concat(
100+
avvmUtxos,
101+
nonAvvmUtxos,
102+
)
103+
return ret, nil
104+
}
105+
106+
func (g *ByronGenesis) avvmUtxos() ([]common.Utxo, error) {
107+
var ret []common.Utxo
108+
for pubkey, amount := range g.AvvmDistr {
109+
// Build address from redeem pubkey
110+
pubkeyBytes, err := base64.URLEncoding.DecodeString(pubkey)
111+
if err != nil {
112+
return nil, err
113+
}
114+
tmpAddr, err := common.NewByronAddressRedeem(
115+
pubkeyBytes,
116+
// XXX: do we need to specify the network ID?
117+
common.ByronAddressAttributes{},
118+
)
119+
if err != nil {
120+
return nil, err
121+
}
122+
tmpAmount, err := strconv.ParseUint(amount, 10, 64)
123+
if err != nil {
124+
return nil, err
125+
}
126+
addrBytes, err := cbor.Encode(tmpAddr)
127+
if err != nil {
128+
return nil, err
129+
}
130+
ret = append(
131+
ret,
132+
common.Utxo{
133+
Id: ByronTransactionInput{
134+
TxId: common.Blake2b256Hash(addrBytes),
135+
OutputIndex: 0,
136+
},
137+
Output: ByronTransactionOutput{
138+
OutputAddress: tmpAddr,
139+
OutputAmount: tmpAmount,
140+
},
141+
},
142+
)
143+
}
144+
return ret, nil
145+
}
146+
147+
func (g *ByronGenesis) nonAvvmUtxos() ([]common.Utxo, error) {
148+
var ret []common.Utxo
149+
for address, amount := range g.NonAvvmBalances {
150+
tmpAddr, err := common.NewAddress(address)
151+
if err != nil {
152+
return nil, err
153+
}
154+
tmpAmount, err := strconv.ParseUint(amount, 10, 64)
155+
if err != nil {
156+
return nil, err
157+
}
158+
addrBytes, err := cbor.Encode(tmpAddr)
159+
if err != nil {
160+
return nil, err
161+
}
162+
ret = append(
163+
ret,
164+
common.Utxo{
165+
Id: ByronTransactionInput{
166+
TxId: common.Blake2b256Hash(addrBytes),
167+
OutputIndex: 0,
168+
},
169+
Output: ByronTransactionOutput{
170+
OutputAddress: tmpAddr,
171+
OutputAmount: tmpAmount,
172+
},
173+
},
174+
)
175+
}
176+
return ret, nil
177+
}
178+
84179
func NewByronGenesisFromReader(r io.Reader) (ByronGenesis, error) {
85180
var ret ByronGenesis
86181
dec := json.NewDecoder(r)

ledger/byron/genesis_test.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
package byron_test
1616

1717
import (
18+
"encoding/json"
1819
"reflect"
20+
"strconv"
1921
"strings"
2022
"testing"
2123

@@ -97,7 +99,6 @@ const byronGenesisConfig = `
9799
`
98100

99101
var expectedGenesisObj = byron.ByronGenesis{
100-
// TODO
101102
AvvmDistr: map[string]string{
102103
"-0BJDi-gauylk4LptQTgjMeo7kY9lTCbZv12vwOSTZk=": "9999300000000",
103104
"-0Np4pyTOWF26iXWVIvu6fhz9QupwWRS2hcCaOEYlw0=": "3760024000000",
@@ -188,7 +189,7 @@ var expectedGenesisObj = byron.ByronGenesis{
188189
Omega: 0,
189190
},
190191
},
191-
NonAvvmBalances: map[string]interface{}{},
192+
NonAvvmBalances: map[string]string{},
192193
VssCerts: map[string]byron.ByronGenesisVssCert{
193194
"0efd6f3b2849d5baf25b3e2bf2d46f88427b4e455fc3dc43f57819c5": {
194195
ExpiryEpoch: 5,
@@ -250,3 +251,84 @@ func TestGenesisFromJson(t *testing.T) {
250251
)
251252
}
252253
}
254+
255+
func TestGenesisNonAvvmUtxos(t *testing.T) {
256+
testAddr := "FHnt4NL7yPXvDWHa8bVs73UEUdJd64VxWXSFNqetECtYfTd9TtJguJ14Lu3feth"
257+
testAmount := uint64(30_000_000_000_000_000)
258+
expectedTxId := "4843cf2e582b2f9ce37600e5ab4cc678991f988f8780fed05407f9537f7712bd"
259+
// Generate genesis config JSON
260+
tmpGenesisData := map[string]any{
261+
"nonAvvmBalances": map[string]string{
262+
testAddr: strconv.FormatUint(testAmount, 10),
263+
},
264+
}
265+
tmpGenesisJson, err := json.Marshal(tmpGenesisData)
266+
if err != nil {
267+
t.Fatalf("unexpected error: %s", err)
268+
}
269+
// Parse genesis config JSON
270+
tmpGenesis, err := byron.NewByronGenesisFromReader(
271+
strings.NewReader(string(tmpGenesisJson)),
272+
)
273+
if err != nil {
274+
t.Fatalf("unexpected error: %s", err)
275+
}
276+
tmpGenesisUtxos, err := tmpGenesis.GenesisUtxos()
277+
if err != nil {
278+
t.Fatalf("unexpected error: %s", err)
279+
}
280+
if len(tmpGenesisUtxos) != 1 {
281+
t.Fatalf("did not get expected count of genesis UTxOs")
282+
}
283+
tmpUtxo := tmpGenesisUtxos[0]
284+
if tmpUtxo.Id.Id().String() != expectedTxId {
285+
t.Fatalf("did not get expected TxID: got %s, wanted %s", tmpUtxo.Id.Id().String(), expectedTxId)
286+
}
287+
if tmpUtxo.Output.Address().String() != testAddr {
288+
t.Fatalf("did not get expected address: got %s, wanted %s", tmpUtxo.Output.Address().String(), testAddr)
289+
}
290+
if tmpUtxo.Output.Amount() != testAmount {
291+
t.Fatalf("did not get expected amount: got %d, wanted %d", tmpUtxo.Output.Amount(), testAmount)
292+
}
293+
}
294+
295+
func TestGenesisAvvmUtxos(t *testing.T) {
296+
testPubkey := "URVk8FxX6Ik9z-Cub09oOxMkp6FwNq27kJUXbjJnfsQ="
297+
testAmount := uint64(2463071701000000)
298+
expectedTxId := "0ae3da29711600e94a33fb7441d2e76876a9a1e98b5ebdefbf2e3bc535617616"
299+
expectedAddr := "Ae2tdPwUPEZKQuZh2UndEoTKEakMYHGNjJVYmNZgJk2qqgHouxDsA5oT83n"
300+
// Generate genesis config JSON
301+
tmpGenesisData := map[string]any{
302+
"avvmDistr": map[string]string{
303+
testPubkey: strconv.FormatUint(testAmount, 10),
304+
},
305+
}
306+
tmpGenesisJson, err := json.Marshal(tmpGenesisData)
307+
if err != nil {
308+
t.Fatalf("unexpected error: %s", err)
309+
}
310+
// Parse genesis config JSON
311+
tmpGenesis, err := byron.NewByronGenesisFromReader(
312+
strings.NewReader(string(tmpGenesisJson)),
313+
)
314+
if err != nil {
315+
t.Fatalf("unexpected error: %s", err)
316+
}
317+
tmpGenesisUtxos, err := tmpGenesis.GenesisUtxos()
318+
if err != nil {
319+
t.Fatalf("unexpected error: %s", err)
320+
}
321+
if len(tmpGenesisUtxos) != 1 {
322+
t.Fatalf("did not get expected count of genesis UTxOs")
323+
}
324+
tmpUtxo := tmpGenesisUtxos[0]
325+
if tmpUtxo.Id.Id().String() != expectedTxId {
326+
t.Fatalf("did not get expected TxID: got %s, wanted %s", tmpUtxo.Id.Id().String(), expectedTxId)
327+
}
328+
if tmpUtxo.Output.Address().String() != expectedAddr {
329+
t.Fatalf("did not get expected address: got %s, wanted %s", tmpUtxo.Output.Address().String(), expectedAddr)
330+
}
331+
if tmpUtxo.Output.Amount() != testAmount {
332+
t.Fatalf("did not get expected amount: got %d, wanted %d", tmpUtxo.Output.Amount(), testAmount)
333+
}
334+
}

ledger/common/address.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/blinklabs-io/gouroboros/base58"
2323
"github.com/blinklabs-io/gouroboros/bech32"
2424
"github.com/blinklabs-io/gouroboros/cbor"
25+
"golang.org/x/crypto/sha3"
2526
)
2627

2728
const (
@@ -135,6 +136,38 @@ func NewByronAddressFromParts(
135136
}, nil
136137
}
137138

139+
func NewByronAddressRedeem(
140+
pubkey []byte,
141+
attr ByronAddressAttributes,
142+
) (Address, error) {
143+
if len(pubkey) != 32 {
144+
return Address{}, fmt.Errorf(
145+
"invalid redeem pubkey length: %d",
146+
len(pubkey),
147+
)
148+
}
149+
addrRoot := []any{
150+
ByronAddressTypeRedeem,
151+
[]any{
152+
ByronAddressTypeRedeem,
153+
pubkey,
154+
},
155+
attr,
156+
}
157+
addrRootBytes, err := cbor.Encode(addrRoot)
158+
if err != nil {
159+
return Address{}, err
160+
}
161+
sha3Sum := sha3.Sum256(addrRootBytes)
162+
addrHash := Blake2b224Hash(sha3Sum[:])
163+
return Address{
164+
addressType: AddressTypeByron,
165+
paymentAddress: addrHash.Bytes(),
166+
byronAddressType: ByronAddressTypeRedeem,
167+
byronAddressAttr: attr,
168+
}, nil
169+
}
170+
138171
func (a *Address) populateFromBytes(data []byte) error {
139172
// Extract header info
140173
header := data[0]

0 commit comments

Comments
 (0)