-
Notifications
You must be signed in to change notification settings - Fork 651
/
static_service.go
377 lines (337 loc) · 12 KB
/
static_service.go
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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package api
import (
"errors"
"fmt"
"net/http"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/formatting"
"github.com/ava-labs/avalanchego/utils/formatting/address"
"github.com/ava-labs/avalanchego/utils/json"
"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/platformvm/genesis"
"github.com/ava-labs/avalanchego/vms/platformvm/signer"
"github.com/ava-labs/avalanchego/vms/platformvm/stakeable"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/platformvm/txs/txheap"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
)
// Note that since an Avalanche network has exactly one Platform Chain,
// and the Platform Chain defines the genesis state of the network
// (who is staking, which chains exist, etc.), defining the genesis
// state of the Platform Chain is the same as defining the genesis
// state of the network.
var (
errUTXOHasNoValue = errors.New("genesis UTXO has no value")
errValidatorHasNoWeight = errors.New("validator has not weight")
errValidatorAlreadyExited = errors.New("validator would have already unstaked")
errStakeOverflow = errors.New("validator stake exceeds limit")
_ utils.Sortable[UTXO] = UTXO{}
)
// StaticService defines the static API methods exposed by the platform VM
type StaticService struct{}
// UTXO is a UTXO on the Platform Chain that exists at the chain's genesis.
type UTXO struct {
Locktime json.Uint64 `json:"locktime"`
Amount json.Uint64 `json:"amount"`
Address string `json:"address"`
Message string `json:"message"`
}
// TODO can we define this on *UTXO?
func (utxo UTXO) Less(other UTXO) bool {
if utxo.Locktime < other.Locktime {
return true
} else if utxo.Locktime > other.Locktime {
return false
}
if utxo.Amount < other.Amount {
return true
} else if utxo.Amount > other.Amount {
return false
}
utxoAddr, err := bech32ToID(utxo.Address)
if err != nil {
return false
}
otherAddr, err := bech32ToID(other.Address)
if err != nil {
return false
}
return utxoAddr.Less(otherAddr)
}
// TODO: Refactor APIStaker, APIValidators and merge them together for
// PermissionedValidators + PermissionlessValidators.
// APIStaker is the representation of a staker sent via APIs.
// [TxID] is the txID of the transaction that added this staker.
// [Amount] is the amount of tokens being staked.
// [StartTime] is the Unix time when they start staking
// [Endtime] is the Unix time repr. of when they are done staking
// [NodeID] is the node ID of the staker
// [Uptime] is the observed uptime of this staker
type Staker struct {
TxID ids.ID `json:"txID"`
StartTime json.Uint64 `json:"startTime"`
EndTime json.Uint64 `json:"endTime"`
Weight json.Uint64 `json:"weight"`
NodeID ids.NodeID `json:"nodeID"`
// Deprecated: Use Weight instead
// TODO: remove [StakeAmount] after enough time for dependencies to update
StakeAmount *json.Uint64 `json:"stakeAmount,omitempty"`
}
// Owner is the repr. of a reward owner sent over APIs.
type Owner struct {
Locktime json.Uint64 `json:"locktime"`
Threshold json.Uint32 `json:"threshold"`
Addresses []string `json:"addresses"`
}
// PermissionlessValidator is the repr. of a permissionless validator sent over
// APIs.
type PermissionlessValidator struct {
Staker
// Deprecated: RewardOwner has been replaced by ValidationRewardOwner and
// DelegationRewardOwner.
RewardOwner *Owner `json:"rewardOwner,omitempty"`
// The owner of the rewards from the validation period, if applicable.
ValidationRewardOwner *Owner `json:"validationRewardOwner,omitempty"`
// The owner of the rewards from delegations during the validation period,
// if applicable.
DelegationRewardOwner *Owner `json:"delegationRewardOwner,omitempty"`
PotentialReward *json.Uint64 `json:"potentialReward,omitempty"`
AccruedDelegateeReward *json.Uint64 `json:"accruedDelegateeReward,omitempty"`
DelegationFee json.Float32 `json:"delegationFee"`
ExactDelegationFee *json.Uint32 `json:"exactDelegationFee,omitempty"`
Uptime *json.Float32 `json:"uptime,omitempty"`
Connected bool `json:"connected"`
Staked []UTXO `json:"staked,omitempty"`
Signer *signer.ProofOfPossession `json:"signer,omitempty"`
// The delegators delegating to this validator
DelegatorCount *json.Uint64 `json:"delegatorCount,omitempty"`
DelegatorWeight *json.Uint64 `json:"delegatorWeight,omitempty"`
Delegators *[]PrimaryDelegator `json:"delegators,omitempty"`
}
// PermissionedValidator is the repr. of a permissioned validator sent over APIs.
type PermissionedValidator struct {
Staker
// The owner the staking reward, if applicable, will go to
Connected bool `json:"connected"`
Uptime *json.Float32 `json:"uptime,omitempty"`
}
// PrimaryDelegator is the repr. of a primary network delegator sent over APIs.
type PrimaryDelegator struct {
Staker
RewardOwner *Owner `json:"rewardOwner,omitempty"`
PotentialReward *json.Uint64 `json:"potentialReward,omitempty"`
}
// Chain defines a chain that exists
// at the network's genesis.
// [GenesisData] is the initial state of the chain.
// [VMID] is the ID of the VM this chain runs.
// [FxIDs] are the IDs of the Fxs the chain supports.
// [Name] is a human-readable, non-unique name for the chain.
// [SubnetID] is the ID of the subnet that validates the chain
type Chain struct {
GenesisData string `json:"genesisData"`
VMID ids.ID `json:"vmID"`
FxIDs []ids.ID `json:"fxIDs"`
Name string `json:"name"`
SubnetID ids.ID `json:"subnetID"`
}
// BuildGenesisArgs are the arguments used to create
// the genesis data of the Platform Chain.
// [NetworkID] is the ID of the network
// [UTXOs] are the UTXOs on the Platform Chain that exist at genesis.
// [Validators] are the validators of the primary network at genesis.
// [Chains] are the chains that exist at genesis.
// [Time] is the Platform Chain's time at network genesis.
type BuildGenesisArgs struct {
AvaxAssetID ids.ID `json:"avaxAssetID"`
NetworkID json.Uint32 `json:"networkID"`
UTXOs []UTXO `json:"utxos"`
Validators []PermissionlessValidator `json:"validators"`
Chains []Chain `json:"chains"`
Time json.Uint64 `json:"time"`
InitialSupply json.Uint64 `json:"initialSupply"`
Message string `json:"message"`
Encoding formatting.Encoding `json:"encoding"`
}
// BuildGenesisReply is the reply from BuildGenesis
type BuildGenesisReply struct {
Bytes string `json:"bytes"`
Encoding formatting.Encoding `json:"encoding"`
}
// beck32ToID takes bech32 address and produces a shortID
func bech32ToID(addrStr string) (ids.ShortID, error) {
_, addrBytes, err := address.ParseBech32(addrStr)
if err != nil {
return ids.ShortID{}, err
}
return ids.ToShortID(addrBytes)
}
// BuildGenesis build the genesis state of the Platform Chain (and thereby the Avalanche network.)
func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, reply *BuildGenesisReply) error {
// Specify the UTXOs on the Platform chain that exist at genesis.
utxos := make([]*genesis.UTXO, 0, len(args.UTXOs))
for i, apiUTXO := range args.UTXOs {
if apiUTXO.Amount == 0 {
return errUTXOHasNoValue
}
addrID, err := bech32ToID(apiUTXO.Address)
if err != nil {
return err
}
utxo := avax.UTXO{
UTXOID: avax.UTXOID{
TxID: ids.Empty,
OutputIndex: uint32(i),
},
Asset: avax.Asset{ID: args.AvaxAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: uint64(apiUTXO.Amount),
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Threshold: 1,
Addrs: []ids.ShortID{addrID},
},
},
}
if apiUTXO.Locktime > args.Time {
utxo.Out = &stakeable.LockOut{
Locktime: uint64(apiUTXO.Locktime),
TransferableOut: utxo.Out.(avax.TransferableOut),
}
}
messageBytes, err := formatting.Decode(args.Encoding, apiUTXO.Message)
if err != nil {
return fmt.Errorf("problem decoding UTXO message bytes: %w", err)
}
utxos = append(utxos, &genesis.UTXO{
UTXO: utxo,
Message: messageBytes,
})
}
// Specify the validators that are validating the primary network at genesis.
vdrs := txheap.NewByEndTime()
for _, vdr := range args.Validators {
weight := uint64(0)
stake := make([]*avax.TransferableOutput, len(vdr.Staked))
utils.Sort(vdr.Staked)
for i, apiUTXO := range vdr.Staked {
addrID, err := bech32ToID(apiUTXO.Address)
if err != nil {
return err
}
utxo := &avax.TransferableOutput{
Asset: avax.Asset{ID: args.AvaxAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: uint64(apiUTXO.Amount),
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Threshold: 1,
Addrs: []ids.ShortID{addrID},
},
},
}
if apiUTXO.Locktime > args.Time {
utxo.Out = &stakeable.LockOut{
Locktime: uint64(apiUTXO.Locktime),
TransferableOut: utxo.Out,
}
}
stake[i] = utxo
newWeight, err := math.Add64(weight, uint64(apiUTXO.Amount))
if err != nil {
return errStakeOverflow
}
weight = newWeight
}
if weight == 0 {
return errValidatorHasNoWeight
}
if uint64(vdr.EndTime) <= uint64(args.Time) {
return errValidatorAlreadyExited
}
owner := &secp256k1fx.OutputOwners{
Locktime: uint64(vdr.RewardOwner.Locktime),
Threshold: uint32(vdr.RewardOwner.Threshold),
}
for _, addrStr := range vdr.RewardOwner.Addresses {
addrID, err := bech32ToID(addrStr)
if err != nil {
return err
}
owner.Addrs = append(owner.Addrs, addrID)
}
utils.Sort(owner.Addrs)
delegationFee := uint32(0)
if vdr.ExactDelegationFee != nil {
delegationFee = uint32(*vdr.ExactDelegationFee)
}
tx := &txs.Tx{Unsigned: &txs.AddValidatorTx{
BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
NetworkID: uint32(args.NetworkID),
BlockchainID: ids.Empty,
}},
Validator: txs.Validator{
NodeID: vdr.NodeID,
Start: uint64(args.Time),
End: uint64(vdr.EndTime),
Wght: weight,
},
StakeOuts: stake,
RewardsOwner: owner,
DelegationShares: delegationFee,
}}
if err := tx.Initialize(txs.GenesisCodec); err != nil {
return err
}
vdrs.Add(tx)
}
// Specify the chains that exist at genesis.
chains := []*txs.Tx{}
for _, chain := range args.Chains {
genesisBytes, err := formatting.Decode(args.Encoding, chain.GenesisData)
if err != nil {
return fmt.Errorf("problem decoding chain genesis data: %w", err)
}
tx := &txs.Tx{Unsigned: &txs.CreateChainTx{
BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
NetworkID: uint32(args.NetworkID),
BlockchainID: ids.Empty,
}},
SubnetID: chain.SubnetID,
ChainName: chain.Name,
VMID: chain.VMID,
FxIDs: chain.FxIDs,
GenesisData: genesisBytes,
SubnetAuth: &secp256k1fx.Input{},
}}
if err := tx.Initialize(txs.GenesisCodec); err != nil {
return err
}
chains = append(chains, tx)
}
validatorTxs := vdrs.List()
// genesis holds the genesis state
g := genesis.Genesis{
UTXOs: utxos,
Validators: validatorTxs,
Chains: chains,
Timestamp: uint64(args.Time),
InitialSupply: uint64(args.InitialSupply),
Message: args.Message,
}
// Marshal genesis to bytes
bytes, err := genesis.Codec.Marshal(genesis.Version, g)
if err != nil {
return fmt.Errorf("couldn't marshal genesis: %w", err)
}
reply.Bytes, err = formatting.Encode(args.Encoding, bytes)
if err != nil {
return fmt.Errorf("couldn't encode genesis as string: %w", err)
}
reply.Encoding = args.Encoding
return nil
}