summaryrefslogtreecommitdiff
path: root/data/bookkeeping/genesis.go
blob: 8220d5232ac6ee85e8cf68db89e74027687c6bfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// Copyright (C) 2019-2024 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package bookkeeping

import (
	"fmt"
	"os"
	"time"

	"github.com/algorand/go-algorand/config"
	"github.com/algorand/go-algorand/crypto"
	"github.com/algorand/go-algorand/crypto/merklesignature"
	"github.com/algorand/go-algorand/data/basics"
	"github.com/algorand/go-algorand/data/committee"
	"github.com/algorand/go-algorand/data/transactions"
	"github.com/algorand/go-algorand/protocol"
)

const (
	// MaxInitialGenesisAllocationSize is the maximum number of accounts that are supported when
	// bootstrapping a new network. The number of account *can* grow further after the bootstrapping.
	// This value is used exclusively for the messagepack decoder, and has no affect on the network
	// capabilities/capacity in any way.
	MaxInitialGenesisAllocationSize = 100000000
)

// A Genesis object defines an Algorand "universe" -- a set of nodes that can
// talk to each other, agree on the ledger contents, etc.  This is defined
// by the initial account states (GenesisAllocation), the initial
// consensus protocol (GenesisProto), and the schema of the ledger.
type Genesis struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	// The SchemaID allows nodes to store data specific to a particular
	// universe (in case of upgrades at development or testing time),
	// and as an optimization to quickly check if two nodes are in
	// the same universe.
	SchemaID string `codec:"id"`

	// Network identifies the unique algorand network for which the ledger
	// is valid.
	// Note the Network name should not include a '-', as we generate the
	// GenesisID from "<Network>-<SchemaID>"; the '-' makes it easy
	// to distinguish between the network and schema.
	Network protocol.NetworkID `codec:"network"`

	// Proto is the consensus protocol in use at the genesis block.
	Proto protocol.ConsensusVersion `codec:"proto"`

	// Allocation determines the initial accounts and their state.
	Allocation []GenesisAllocation `codec:"alloc,allocbound=MaxInitialGenesisAllocationSize"`

	// RewardsPool is the address of the rewards pool.
	RewardsPool string `codec:"rwd"`

	// FeeSink is the address of the fee sink.
	FeeSink string `codec:"fees"`

	// Timestamp for the genesis block
	Timestamp int64 `codec:"timestamp"`

	// Arbitrary genesis comment string - will be excluded from file if empty
	Comment string `codec:"comment"`

	// DevMode defines whether this network operates in a developer mode or not. Developer mode networks
	// are a single node network, that operates without the agreement service being active. In liue of the
	// agreement service, a new block is generated each time a node receives a transaction group. The
	// default value for this field is "false", which makes this field empty from it's encoding, and
	// therefore backward compatible.
	DevMode bool `codec:"devmode"`
}

// LoadGenesisFromFile attempts to load a Genesis structure from a (presumably) genesis.json file.
func LoadGenesisFromFile(genesisFile string) (genesis Genesis, err error) {
	// Load genesis.json
	genesisText, err := os.ReadFile(genesisFile)
	if err != nil {
		return
	}

	err = protocol.DecodeJSON(genesisText, &genesis)
	return
}

// ID is the effective Genesis identifier - the combination
// of the network and the ledger schema version
func (genesis Genesis) ID() string {
	return string(genesis.Network) + "-" + genesis.SchemaID
}

// Hash is the genesis hash.
func (genesis Genesis) Hash() crypto.Digest {
	return crypto.HashObj(genesis)
}

// Balances returns the genesis account balances.
func (genesis Genesis) Balances() (GenesisBalances, error) {
	genalloc := make(map[basics.Address]basics.AccountData)
	for _, entry := range genesis.Allocation {
		addr, err := basics.UnmarshalChecksumAddress(entry.Address)
		if err != nil {
			return GenesisBalances{}, fmt.Errorf("cannot parse genesis addr %s: %w", entry.Address, err)
		}

		_, present := genalloc[addr]
		if present {
			return GenesisBalances{}, fmt.Errorf("repeated allocation to %s", entry.Address)
		}

		genalloc[addr] = entry.State.AccountData()
	}

	feeSink, err := basics.UnmarshalChecksumAddress(genesis.FeeSink)
	if err != nil {
		return GenesisBalances{}, fmt.Errorf("cannot parse fee sink addr %s: %w", genesis.FeeSink, err)
	}

	rewardsPool, err := basics.UnmarshalChecksumAddress(genesis.RewardsPool)
	if err != nil {
		return GenesisBalances{}, fmt.Errorf("cannot parse rewards pool addr %s: %w", genesis.RewardsPool, err)
	}

	return MakeTimestampedGenesisBalances(genalloc, feeSink, rewardsPool, genesis.Timestamp), nil
}

// Block computes the genesis block.
func (genesis Genesis) Block() (Block, error) {
	genBal, err := genesis.Balances()
	if err != nil {
		return Block{}, err
	}

	return MakeGenesisBlock(genesis.Proto, genBal, genesis.ID(), genesis.Hash())
}

// A GenesisAllocation object represents an allocation of algos to
// an address in the genesis block.  Address is the checksummed
// short address.  Comment is a note about what this address is
// representing, and is purely informational.  State is the initial
// account state.
type GenesisAllocation struct {
	// Unfortunately we forgot to specify omitempty, and now
	// this struct must be encoded without omitempty for the
	// Address, Comment, and State fields..
	_struct struct{} `codec:""`

	Address string             `codec:"addr"`
	Comment string             `codec:"comment"`
	State   GenesisAccountData `codec:"state"`
}

// ToBeHashed impements the crypto.Hashable interface.
func (genesis Genesis) ToBeHashed() (protocol.HashID, []byte) {
	return protocol.Genesis, protocol.Encode(&genesis)
}

// GenesisBalances contains the information needed to generate a new ledger
type GenesisBalances struct {
	Balances    map[basics.Address]basics.AccountData
	FeeSink     basics.Address
	RewardsPool basics.Address
	Timestamp   int64
}

// GenesisAccountData contains a subset of account information that is present in the genesis file.
type GenesisAccountData struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	Status          basics.Status                   `codec:"onl"`
	MicroAlgos      basics.MicroAlgos               `codec:"algo"`
	VoteID          crypto.OneTimeSignatureVerifier `codec:"vote"`
	StateProofID    merklesignature.Commitment      `codec:"stprf"`
	SelectionID     crypto.VRFVerifier              `codec:"sel"`
	VoteFirstValid  basics.Round                    `codec:"voteFst"`
	VoteLastValid   basics.Round                    `codec:"voteLst"`
	VoteKeyDilution uint64                          `codec:"voteKD"`
}

// AccountData returns a basics.AccountData type for this genesis account.
func (ga *GenesisAccountData) AccountData() basics.AccountData {
	return basics.AccountData{
		Status:          ga.Status,
		MicroAlgos:      ga.MicroAlgos,
		VoteID:          ga.VoteID,
		StateProofID:    ga.StateProofID,
		SelectionID:     ga.SelectionID,
		VoteFirstValid:  ga.VoteFirstValid,
		VoteLastValid:   ga.VoteLastValid,
		VoteKeyDilution: ga.VoteKeyDilution,
	}
}

// MakeGenesisBalances returns the information needed to bootstrap the ledger based on the current time
func MakeGenesisBalances(balances map[basics.Address]basics.AccountData, feeSink, rewardsPool basics.Address) GenesisBalances {
	return MakeTimestampedGenesisBalances(balances, feeSink, rewardsPool, time.Now().Unix())
}

// MakeTimestampedGenesisBalances returns the information needed to bootstrap the ledger based on a given time
func MakeTimestampedGenesisBalances(balances map[basics.Address]basics.AccountData, feeSink, rewardsPool basics.Address, timestamp int64) GenesisBalances {
	return GenesisBalances{Balances: balances, FeeSink: feeSink, RewardsPool: rewardsPool, Timestamp: timestamp}
}

// MakeGenesisBlock creates a genesis block, including setup of RewardsState.
func MakeGenesisBlock(proto protocol.ConsensusVersion, genesisBal GenesisBalances, genesisID string, genesisHash crypto.Digest) (Block, error) {
	params, ok := config.Consensus[proto]
	if !ok {
		return Block{}, fmt.Errorf("unsupported protocol %s", proto)
	}

	genesisRewardsState := RewardsState{
		FeeSink:                   genesisBal.FeeSink,
		RewardsPool:               genesisBal.RewardsPool,
		RewardsLevel:              0,
		RewardsResidue:            0,
		RewardsRecalculationRound: basics.Round(params.RewardsRateRefreshInterval),
	}

	initialRewards := genesisBal.Balances[genesisBal.RewardsPool].MicroAlgos.Raw
	if params.InitialRewardsRateCalculation {
		genesisRewardsState.RewardsRate = basics.SubSaturate(initialRewards, params.MinBalance) / uint64(params.RewardsRateRefreshInterval)
	} else {
		genesisRewardsState.RewardsRate = initialRewards / uint64(params.RewardsRateRefreshInterval)
	}

	blk := Block{
		BlockHeader: BlockHeader{
			Round:          0,
			Branch:         BlockHash{},
			Seed:           committee.Seed(genesisHash),
			TxnCommitments: TxnCommitments{NativeSha512_256Commitment: transactions.Payset{}.CommitGenesis(), Sha256Commitment: crypto.Digest{}},
			TimeStamp:      genesisBal.Timestamp,
			GenesisID:      genesisID,
			RewardsState:   genesisRewardsState,
			UpgradeState: UpgradeState{
				CurrentProtocol: proto,
			},
			UpgradeVote: UpgradeVote{},
		},
	}

	// If a new network is being created in which AVM can't access low numbered
	// resources, bump the TxnCounter so there won't be any such resources.
	if params.AppForbidLowResources {
		blk.TxnCounter = 1000
	}

	if params.SupportGenesisHash {
		blk.BlockHeader.GenesisHash = genesisHash
	}

	return blk, nil
}