-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
618 lines (508 loc) · 30.4 KB
/
main.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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
// Package main is the entry point of the program. It uses libp2p to start connecting to other peers to sync the current state of the blockchain and receive/work on new problem definitions and listen to other events.
package main
import (
"os/exec"
"runtime"
"github.com/felix314159/gophy/database"
"github.com/felix314159/gophy/githubversioncheck"
"github.com/felix314159/gophy/logger"
// /* this entire block is commented out when you want to use pseudo.go for getting large pseudo blockchain for testing
"context"
"crypto/ed25519"
"flag"
"fmt"
"path/filepath"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/felix314159/gophy/block/simsol"
"github.com/felix314159/gophy/httpapi"
"github.com/felix314159/gophy/monitoring"
// libp2p stuff
//connmgr "github.com/libp2p/go-libp2p/p2p/net/connmgr"
libp2p "github.com/libp2p/go-libp2p"
dht "github.com/libp2p/go-libp2p-kad-dht"
pubsub "github.com/libp2p/go-libp2p-pubsub"
rsrcmngr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
// */
)
const version = "v0.9.32"
// Example RA Node ID: 12D3KooWEYSb69dzeojEH1PygPWef9V1qQJqrGKUEMMsbA4keAyZ
// EXAMPLE USAGE:
// RA: go run . -syncMode=SyncMode_Continuous_Full -httpPort=8097 -localKeyFile=true -raMode=true -raReset=true -pw=supersecretrapw -dockerAlias=ra
// Miner: go run . -syncMode=SyncMode_Initial_Mine -httpPort=8098 -dockerAlias=someminer
// Full Node: go run . -syncMode=SyncMode_Initial_Full -httpPort=8099 -dockerAlias=somefullnode
// Note: This is a development version where RA has 30k tokens pre-allocated in genesis block. Needs to be removed before production usage.
func main() {
// /*
// ---- Create database folder if it does not exist already ----
simsol.CreateFolder(filepath.Join(".", "database"))
// ---- Define flags (flagName, defaultValue, description) ----
// flags for networking / keeps running
topicNamesFlag := flag.String("topicNames", "pouw_chaindb,pouw_newProblem,pouw_newBlock,pouw_minerCommitments,pouw_transactions,pouw_raSecretReveal", "Choose which topics to subscribe to. Must be one string that is comma-separated, contains no spaces and no : chars.")
syncModeFlag := flag.String("syncMode", "SyncMode_Initial_Full", "Sets the SyncMode which affects which blockchain data is stored locally, how the node responds to other nodes and generally how it behaves in database. Valid choices are SyncMode_Initial_Full, SyncMode_Initial_Light, SyncMode_Initial_Mine, SyncMode_Continuous_Full, SyncMode_Continuous_Light and SyncMode_Continuous_Mine.")
raFlag := flag.Bool("raMode", false, "Run as RA and sends them simulation task block problems over time. Only possible if you know private key of RA.")
raResetFlag := flag.Bool("raReset", false, "Lets RA start new blockchain from scratch (it can't use Initial mode to perform this).")
localKeyFileFlag := flag.Bool("localKeyFile", false, "Re-uses locally available priv and pubkey instead of generating new keys.")
httpPortFlag := flag.Int("httpPort", 8087, "Sets the port that the two local websites for sending simtasks and transactions runs on.")
pwFlag := flag.String("pw", "insecurepassword", "Defines password used to encrypt private key in OpenSSH format. Never use this default password in production (it is just useful for automated Docker tests).")
dockerAliasFlag := flag.String("dockerAlias", "mynode", "Sets the name of the docker instance that runs this node. Useful for having performance stats recording where Sender of a stat can easily by identified via its docker alias.")
// flags that do something locally and exit
dumpFlag := flag.String("dump", "nil", "Takes provided blockHash or 'latest' string and retrieves the block from local chaindb. Then prints its header if the block was available.")
dumpStateFlag := flag.Bool("dumpState", false, "Prints all wallet information (nonce and token balance of all accounts in statedb. Light nodes can not use this flag because they do not locally store the state. This function takes precendence over the chaindb dump, so only use one at a time.")
// ---- Parse flags ----
flag.Parse()
if *dumpFlag == "nil" && !(*dumpStateFlag) {
// separate different executions in the log, also list used flags
logger.L.Printf("---- STARTING EXECUTION (%v) ----\nFlag values used:\n\ttopicNames: %v\n\tsyncMode: %v\n\traMode: %v\n\traReset: %v\n\tlocalKeyFile: %v\n\thttpPort: %v\n\tdockerAlias: %v\n\tdump: %v", version, *topicNamesFlag, *syncModeFlag, *raFlag, *raResetFlag, *localKeyFileFlag, *httpPortFlag, *dockerAliasFlag, *dumpFlag) // never log the password
}
// ---- Handle flags ----
// Check whether the currently run version of gophy is the newest available release on github (you are allowed to use older versions, just used to inform the user)
// Only perform this check when you are NOT running this in a docker scratch container (that container does not have CA certficates so it would fail with 'tls: failed to verify certificate: x509: certificate signed by unknown authority')
if (*dockerAliasFlag != "1f") && (*dockerAliasFlag != "2f") && (*dockerAliasFlag != "3f") && (*dockerAliasFlag != "4l") && (*dockerAliasFlag != "5l") && (*dockerAliasFlag != "6l") && (*dockerAliasFlag != "ra") {
isUsingNewestGophyVersion, latestVersion, err := githubversioncheck.IsUsingNewestGophyVersion(version)
if err != nil {
logger.L.Printf("Failed to determine latest release of gophy: %v", err)
}
if isUsingNewestGophyVersion {
logger.L.Printf("You are running the latest version of gophy: %v\n", version)
} else {
logger.L.Printf("New version of gophy is available: %v\nYou are still using version: %v\nPlease update it manually!\n", latestVersion, version)
}
}
// only allow user ports to avoid port conflicts
database.HttpPort = *httpPortFlag
if (database.HttpPort < 1024) || (database.HttpPort > 49151) || (database.HttpPort == 12345) {
logger.L.Printf("Please set the httpPort flag to a value in [1024, 49151]. Also avoid port 12345 (already used for performance stat server). Aborting program execution.")
return
}
// warn user if default password 'insecurepassword' (just useful for running many docker nodes at once) [do not warn if user is just dumping some data]
if *pwFlag == "insecurepassword" && *dumpFlag == "nil" && !(*dumpStateFlag) {
logger.L.Printf("WARNING - You are using the default password, this is allowed but not recommended.") // even if you re-use your key and that key uses the default password, you will get reminded every time. this is intended
}
// remember your docker instance alias
database.MyDockerAlias = *dockerAliasFlag
// handle syncMode flag
// check whether given syncMode is valid
curNodeMode, err := database.StringToMode(*syncModeFlag)
if err != nil {
logger.L.Printf("Invalid syncModeFlag value provided: %v\nTerminating..", err)
return
}
// while it internally exists, you are not allowed to set the target sync mode to passive
if curNodeMode.String() == "SyncMode_Passive_Full" || curNodeMode.String() == "SyncMode_Passive_Light" {
logger.L.Printf("Invalid syncModeFlag value provided: %v\nYou are not allowed to explicitely start the node in passive mode! Terminating..", err)
return
}
// set IAmRA value (defaults to false)
if *raFlag {
database.IAmRA = true
}
// a full node always stays a full node while running, a light node always stays a light node while running. so its useful to store this seperately for easy access
// also: if continuous or passive was chosen, ensure that it's the correct one (check if local blockchain is full or light and if that is compatible with chosen continuous mode)
switch curNodeMode {
case database.SyncMode_Initial_Full, database.SyncMode_Initial_Mine:
database.IAmFullNode = true
case database.SyncMode_Initial_Light:
database.IAmFullNode = false
case database.SyncMode_Continuous_Full, database.SyncMode_Continuous_Mine, database.SyncMode_Passive_Full:
database.IAmFullNode = true
// ensure that mode is possible
localDbIsFullNode := database.BoltBucketExists("statedb")
if !localDbIsFullNode { // if you are a light node or the RA but without reset flag, then exit with warning
if !(database.IAmRA) {
logger.L.Printf("You do not seem to be a full node.. You either are a new node that does not have a database yet (in this case: use -syncMode=SyncMode_Initial_Mine) or you are a light node but you tried to run with %v but this is not allowed! \n", curNodeMode)
return
} else { // you are the RA, arriving here is only allowed when you have set the raReset flag to true
if !(*raResetFlag) {
logger.L.Printf("You are the RA but you have no statedb and you forgot to set the raReset flag to true.. Not allowed, exiting..")
return
}
}
}
case database.SyncMode_Continuous_Light, database.SyncMode_Passive_Light:
database.IAmFullNode = false
// ensure that mode is allowed (it should not be allowed to suddenly run a full node as a light node to keep things simple)
localDbIsFullNode := database.BoltBucketExists("statedb")
if localDbIsFullNode {
logger.L.Printf("You are a full node but you tried to run with %v! This is not allowed, run it with _Full or _Mine (does not exist for Passive) instead! \n", curNodeMode)
return
}
}
// ensure prerequisites for being a miner are met
if (curNodeMode == database.SyncMode_Continuous_Mine) || (curNodeMode == database.SyncMode_Initial_Mine) {
// 1. ensure user is running linux or macOS (fairrot does not support windows)
if (runtime.GOOS != "linux") && (runtime.GOOS != "darwin") {
logger.L.Panicf("You try to be a miner but you are not running an OS that supports FairSoft. Please try running this code on linux or macOS. Terminating.")
return
}
// 2. if miner: check if PATH env variable contains substring 'fair' or 'sim' (at some point find more robust solution)
pathEnvContent := os.Getenv("PATH")
pathEnvContent = strings.ToLower(pathEnvContent)
if !(strings.Contains(pathEnvContent, "sim") || strings.Contains(pathEnvContent, "fair")) {
logger.L.Panicf("You try to be a miner but querying PATH does not show any indication of fairsoft. Terminating.\nNote: If this is a false positive comment out this check or ensure that you have followed the fairroot tutorial I provided.")
return
}
logger.L.Printf("Presence of fairsoft env has been verified.")
}
// remember initial nodemode
database.OriginalMode = curNodeMode
// print for user whether you are full node or light (but only if this is not just a dump of a block)
if database.IAmFullNode && (*dumpFlag == "nil") {
logger.L.Printf("I am a full node.")
} else if !database.IAmFullNode && (*dumpFlag == "nil") {
logger.L.Printf("I am a light node.")
}
// handle dumpState flag
if *dumpStateFlag {
if !database.IAmFullNode {
logger.L.Printf("You tried to dump the state but you are a light node! This is not possible, terminating..\n")
return
}
database.PrintStateDB()
return
}
// handle dump flag (must be handled first to avoid db reset in case the default syncmode is initial)
dumpBlockKey := *dumpFlag
if dumpBlockKey != "nil" {
// check if provided flag possibly can be valid ( either it is "latest" or "genesis" (with "" or without "" both work) or its just some hash (without "") of length 64 )
if (dumpBlockKey != "latest" && dumpBlockKey != "genesis") && len(dumpBlockKey) != 64 {
logger.L.Printf("Invalid dump flag. Expected 'latest' or 'genesis' or hash hex string of length 64 without leading 0x.")
return
}
// determine whether you are a full or a light node by checking if the statedb bucket exists or not
localDbIsFullNodeDump := database.BoltBucketExists("statedb")
// replace genesis string with actual hash of genesis block
if dumpBlockKey == "genesis" {
dumpBlockKey = database.ChainDbGetGenesisHash(localDbIsFullNodeDump)
}
// try to retrieve block
retrievedBlockBytes, err := database.BlockGetBytesFromDb(dumpBlockKey)
if err != nil {
logger.L.Printf("This block does not seem to exist: %v", err)
return
}
if localDbIsFullNodeDump {
// deserialize block
retrievedBlockObject := database.FullBlockBytesToBlock(retrievedBlockBytes)
// print block, then terminate
database.PrintBlock(retrievedBlockObject)
return
} else {
// deserialize header
retrievedHeaderObject, _ := database.HeaderBytesToBlockLight(retrievedBlockBytes)
// print header, then terminate
database.PrintBlockHeader(retrievedHeaderObject)
return
}
}
// handle topicNames flag
// first split user-given value by , (topic seperation symbol)
topics := strings.Split(*topicNamesFlag, ",") // returns []string
// then append values to TopicSlice
for _, tt := range topics {
if strings.Contains(tt, ":") || strings.Contains(tt, " ") {
logger.L.Panicf("Topics are not allowed to contain ':' or ' '. Terminating..")
}
database.TopicSlice = append(database.TopicSlice, tt)
}
// start performance stats server if it is not running already
go monitoring.StartPerformanceStatsServer()
// ---- Initialize SyncHelper ----
// configure SyncHelper
database.SyncHelper = database.SyncHelperStruct {
NodeMode: curNodeMode,
ConfirmationsReq: 1, // during development this should be 1, but in production (when new nodes try to join an existing network) this value should be increased. it defines how many response confirmations from different nodes have to be received during inital sync until the response is accepted. a higher value should be more secure
Data: make(map[string]struct {
ConfirmationsCur int
Data []byte
}),
Senders: []string{},
}
logger.L.Printf("SyncHelper configured to mode: %v", database.SyncHelper.NodeMode)
// local db will be deleted when sync mode is initial_full/light/mine OR if raResetFlag is set by an RA that sends out problems
if curNodeMode == database.SyncMode_Initial_Full || curNodeMode == database.SyncMode_Initial_Light || curNodeMode == database.SyncMode_Initial_Mine || (database.IAmRA && *raResetFlag){
logger.L.Printf("Resetting database...")
database.ResetAndInitializeGenesis(database.IAmFullNode)
}
// ---- Set-up node ----
// Linux only fix: Set correct buffer size to prevent the following warning:
// Failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB).
// See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.
//
//FixLinuxBuffer()
// let user cancel execution by sending SIGINT or SIGTERM (prevents issue where ctrl+C or ctrl+. don't kill running program)
ctx, cancel := context.WithCancel(context.Background())
sigs := make(chan os.Signal, 1) // avoid unbuffered channel here (https://github.com/golang/lint/issues/175)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
cancel()
}()
// key is stored in ed25519 format so that encrypted encoding works, but at startup is converted to libp2p ed25519 which slightly differs (avoids 'missing method Equal()' issues with priv)
var ed25519Priv ed25519.PrivateKey
var priv crypto.PrivKey
var pub crypto.PubKey
privKeyLocation := "privkey.key"
raPrivKeyLocation := "raprivkey.key"
// check whether user has existing keyfile or wants to generate new keys
if *localKeyFileFlag {
// depending on whether you are RA or not, read priv key from different files (useful to have access to both while testing)
if database.IAmRA {
ed25519Priv = database.ReadKeyFromFileAndDecrypt(*pwFlag, raPrivKeyLocation)
} else {
ed25519Priv = database.ReadKeyFromFileAndDecrypt(*pwFlag, privKeyLocation)
}
logger.L.Printf("Successfully read and decrypted private key.")
} else {
// default: you are a new miner and want to create new priv/pub keypair at this startup
var err error
_, ed25519Priv, err = ed25519.GenerateKey(nil) // nil will use crypto/rand.Reader
if err != nil {
logger.L.Panic(err)
}
}
// convert ed25519 priv to libp2p priv
priv, err = crypto.UnmarshalEd25519PrivateKey(ed25519Priv)
if err != nil {
logger.L.Panicf("Failed to convert ed25519 priv to libp2p priv: %v\n", err)
}
// derive libp2p pub
pub = priv.GetPublic()
// derive libp2p nodeID from public key
database.MyNodeIDString, err = database.PubKeyToNodeID(pub)
if err != nil {
logger.L.Panic(err)
}
// report your libp2p nodeID to performance stats server
perfLibp2pID := monitoring.NewPerformanceData(time.Now().UnixNano(), database.MyDockerAlias, monitoring.Event_Libp2pNodeIDKnown, fmt.Sprintf("My libp2p node ID: %v", database.MyNodeIDString))
err = monitoring.SendPerformanceStatWithRetries(perfLibp2pID)
if err != nil {
logger.L.Printf("Failed to submit my node ID to the performance stats server: %v", err)
}
// if you are RA, ensure that the derived nodeID is equal to the RAnodeID constant defined in constants.go
if database.IAmRA && (database.MyNodeIDString != database.RANodeID) {
logger.L.Panicf("The RA private key you used results in node ID %v but this version of the code sets the const RA node ID to %v. If you intend to use a different RA priv key ensure to also adjust the derived const RA node ID string in constants.go!", database.MyNodeIDString, database.RANodeID)
}
// set PrivateKey (view note about this in constants.go)
database.PrivateKey = priv
// ---- If you are not the RA: Switch into initial sync mode during peer discovery (to avoid distractions from other nodes) ----
if !database.IAmRA {
switch database.OriginalMode {
case database.SyncMode_Continuous_Full:
database.SyncHelper.NodeModeWrite(database.SyncMode_Initial_Full)
logger.L.Printf("Temporarily setting you to %v for your sync.", database.SyncMode_Initial_Full)
case database.SyncMode_Continuous_Light:
database.SyncHelper.NodeModeWrite(database.SyncMode_Initial_Light)
logger.L.Printf("Temporarily setting you to %v for your sync.", database.SyncMode_Initial_Light)
case database.SyncMode_Continuous_Mine:
database.SyncHelper.NodeModeWrite(database.SyncMode_Initial_Mine)
logger.L.Printf("Temporarily setting you to %v for your sync.", database.SyncMode_Initial_Mine)
}
}
// ---- Setup libp2p node ----
// create connection manager
// connMgr, err := connmgr.NewConnManager(
// 200, // low watermark (keep attempting to open connections until you are connected to at least this many peers, it helps to stay well connected)
// 2000, // high watermark (start closing less frequently used active connection as soon as you reach this many active connections)
// )
// if err != nil {
// panic(err)
// }
// prevent DoS attacks by using a limiter, default values are sufficient i think (https://github.com/libp2p/go-libp2p/blob/master/p2p/host/resource-manager/limit_defaults.go#L665)
scalingLimits := rsrcmngr.DefaultLimits
libp2p.SetDefaultServiceLimits(&scalingLimits) // https://github.com/libp2p/go-libp2p/tree/master/p2p/host/resource-manager
limiter := rsrcmngr.NewFixedLimiter(scalingLimits.AutoScale())
// create resourcemanager that makes use of the limiter (no need for prometheus metrics)
resourceManager, err := rsrcmngr.NewResourceManager(limiter, rsrcmngr.WithMetricsDisabled())
if err != nil {
logger.L.Panicf("failed to create resource manager: %v\n", err)
}
// show/log limits used
systemLimits := limiter.GetSystemLimits()
PrintAllResourceLimits(systemLimits, "System Limits")
transientLimits := limiter.GetTransientLimits()
PrintAllResourceLimits(transientLimits, "Transient Limits")
// use ifps team hosted bootstrap servers (https://github.com/ipfs/kubo/blob/master/config/bootstrap_peers.go#L17)
var bootstrapPeers []peer.AddrInfo
for _, multiAddr := range dht.DefaultBootstrapPeers {
// convert each multiaddr.MultiAddr to a peer.AddrInfo
addrInfo, err := peer.AddrInfoFromP2pAddr(multiAddr)
if err != nil {
logger.L.Panicf("failed to convert MultiAddr to AddrInfo: %v", err)
}
bootstrapPeers = append(bootstrapPeers, *addrInfo)
}
// configure node
h, err := libp2p.New(
libp2p.Identity(priv), // use generated private key as identity
libp2p.UserAgent(fmt.Sprintf("pouw/%v", version)), // set agent version so that other nodes know which version of this software this node is running
// options below make node publicly available if possible
// rust tool to verify e.g.: libp2p-lookup dht --network ipfs --peer-id <nodeid> , then it shows the agent version among other listen addresses and supported protocols
libp2p.EnableAutoRelayWithStaticRelays(bootstrapPeers), // become publicly available via ipfs hosted bootstrap nodes that act as relay servers
libp2p.EnableHolePunching(), // enable holepunching
libp2p.NATPortMap(), // try to use upnp to open port in firewall
// use resource manager with limiter
libp2p.ResourceManager(resourceManager),
// ---- this will be used implicitely ----
// libp2p.ListenAddrStrings( // as specified in defaults.go
// "/ip4/0.0.0.0/tcp/0",
// "/ip4/0.0.0.0/udp/0/quic-v1",
// "/ip4/0.0.0.0/udp/0/quic-v1/webtransport",
// "/ip6/::/tcp/0",
// "/ip6/::/udp/0/quic-v1",
// "/ip6/::/udp/0/quic-v1/webtransport",
// ),
//libp2p.DefaultSecurity, // support TLS and Noise
//libp2p.DefaultTransports, // support any default transports (tcp, quic, websocket and webtransport)
//libp2p.DefaultMuxers, // support yamux
//libp2p.DefaultPeerstore, // use default peerstore (probably not necessary)
// ---- tried these but not necessary afaik ----
//libp2p.EnableRelayService(), // if you are publicly available act as a relay and help others behind NAT
//libp2p.EnableNATService(), // if you are publicly available help other to figure out whether they are publicly available or not
//libp2p.EnableRelay(), // makes no difference (because it is enabled by default)
//libp2p.ForceReachabilityPublic(), // do NOT use this when you are behind NAT, i can't even ping my node with this enabled
)
if err != nil {
logger.L.Panic(err)
}
// ---- Set stream handler for direct communication via chat protocol ----
h.SetStreamHandler("/chat/1.0", func(stream network.Stream) {
database.HandleIncomingChatMessage(stream, h, ctx)
})
// ensure that the nodeID you got is equal to the nodeID that should have been derived from the priv key you used
if database.MyNodeIDString != h.ID().String() {
logger.L.Panicf("The libp2p node ID you got is %v but from your private key it was expected that you would get %v. Aborting..", h.ID().String(), database.MyNodeIDString)
}
logger.L.Printf("My node ID: %v", database.MyNodeIDString)
// print node addresses after 3 seconds
// go func() {
// time.Sleep(3 * time.Second)
// logger.L.Printf("I am reachable via these addresses:")
// for _, addr := range h.Addrs() {
// logger.L.Printf("\t%v/p2p/%v", addr, database.MyNodeIDString)
// }
// }()
// derive peer ID from node ID string
myPeerID, err := peer.Decode(database.MyNodeIDString)
if err != nil {
logger.L.Panicf("Failed to derive peer ID from node ID string: %v", err)
}
if myPeerID.String() != database.MyNodeIDString {
logger.L.Panicf("Failed to cast node ID string to peer ID. Calculated node ID %v and actual node ID %v should be equal!", myPeerID.String(), database.MyNodeIDString)
}
// add private and public key to keystore so that they later can be safely accessed when needed
h.Peerstore().AddPrivKey(myPeerID, priv)
h.Peerstore().AddPubKey(myPeerID, pub)
// if you need to retrieve your private key you can do it like below
// h.Peerstore().PrivKey(h.ID())
// or
// h.Peerstore().PrivKey(database.MyNodeIDString)
// or just access
// database.PrivateKey
// if you are not RA: store your private key in an encrypted key file (for RA it is assumed that the keyfile already exists and that everyone knows the nodeID derived from it), unless you have re-used your node identity
if !(database.IAmRA) && !(*localKeyFileFlag) {
comment := "gophy key"
database.EncryptKeyAndWriteToFile(ed25519Priv, *pwFlag, privKeyLocation, comment)
logger.L.Printf("Encrypted private key has been stored as: %v", privKeyLocation)
}
// ---- PubSub topic subscriptions ----
// use gossipsub for message distribution over topics, returns *pubsub.PubSub (using default GossipSubRouter as the router)
ps, err := pubsub.NewGossipSub(ctx, h)
if err != nil {
logger.L.Panic(err)
}
// iterate over topics slice
for _, t := range database.TopicSlice {
topic, err := ps.Join(t) // convert string to *pubsub.Topic (line 1228 of https://github.com/libp2p/go-libp2p-pubsub/blob/master/pubsub.go)
if err != nil {
logger.L.Panic(err)
}
// add topic to database.PubsubTopicSlice
database.PubsubTopicSlice = append(database.PubsubTopicSlice, topic)
// subscribe to topic of interest
sub, err := topic.Subscribe() // returns chan *pubsub.Subscription (line 143 of https://github.com/libp2p/go-libp2p-pubsub/blob/master/topic.go)
if err != nil {
logger.L.Panic(err)
}
logger.L.Printf("Subscribed to topic: %v", t)
// listen for incoming messages on every topic that was subscribed to
go database.TopicReceiveMessage(ctx, sub, h)
}
// ---- Peer Discovery ----
// A lot happens in the background here:
// AFAIK it triggers DHT queries and asks a boostrap node or an early discovered neighbor node whether they know a node who used the same rendezvous string,
// then these nodes ask other nodes [close to them] whether they know and so on.. Out of all the ca. 30k DHT servers only 20 store the
// [hash(rendezvousString), peerID] list we need to know so log_20(30000) ~ 3.44 so this process is expected to be repeated multiple times
// until one of the 20 relevant DHT servers has been found and then they give you their nodeID list
// its very important the these lines below are placed after you subscribed to all topics, otherwise topic messages get ignored (i think when you discover a peer you ask it 'what topics do you care about', so when the subscribing-to-topics would happen after peer discovery no one would receive topic messages because it would not be known that these nodes are subscribed to these topics so no one would try to propagate topic messages to them)
ch := make(chan bool) // unbuffered channel for bool used so that node waits until peers have been discovered before continuing
go database.DiscoverPeers(ctx, h, ch) // find peers using DHT bootstrap servers + Rendezvous
<-ch // wait until you find the min amount of required peers
logger.L.Printf("Initial peer discovery completed!") // continue with program flow (but the discovery keeps running in background and is still looking for new peers)
// ---- Nodes: Start initial sync / RA: Start sending sim tasks ----
// RA uses this function
if database.IAmRA {
// start http server for send-transaction and send-simtask as goroutine
go httpapi.RunServer()
// keep sending new block problems
go database.RALoop(ctx)
} else {
// all non-RA nodes start their initial sync
logger.L.Printf("Starting initial sync..")
initialSyncDone := make(chan struct{})
go database.SyncNode(ctx, h, initialSyncDone)
<-initialSyncDone // wait for initial sync to complete
// after completing initial sync you are connected to enough peers and able to receive transactions or simtasks via the locally hosted websites, so start the http server as goroutine (you should not be allowed to send transactions before you are in sync with the others)
go httpapi.RunServer()
}
// keep node running (otherwise if main terminates all goroutines are killed)
select {}
// */
// err := database.CreatePseudoBlockchainLight(10)
// if err != nil {
// logger.L.Panic(err)
// }
// logger.L.Printf("Created light blockchain.")
// err := database.CreatePseudoBlockchainFull(10, "supersecretrapw", 10)
// if err != nil {
// logger.L.Panic(err)
// }
// logger.L.Printf("Created full blockchain.")
}
// FixLinuxBuffer silences a linux warnung that can occur due to mismatch between available and expected quic buffer size.
// It's not a necessity to perform this fix (the warning does not impact functionality and only occurs on some linux+libp2p combinations) and it requires root to fix, so decide yourself.
func FixLinuxBuffer() {
if (runtime.GOOS != "linux") {
return
}
// define the three commands needed to adjust buffer to recommended size
cmd1 := exec.Command("sudo", "sysctl", "-w", "net.core.rmem_max=2097152") // 2048*1024=2097152
cmd2 := exec.Command("sudo", "sysctl", "-w", "net.core.wmem_max=2097152")
cmd3 := exec.Command("sysctl", "-p")
// tell user to enter password (first two commands must be run as root, but remember: this fix is optional and not even used by default. afaik everything works normally even without it so running as root not worth it IMO)
logger.L.Printf("You are running Linux. Enter root password to silence buffer warning:")
// run commands
if err := cmd1.Run(); err != nil {
logger.L.Printf("Failed to adjust buffer size: %v\n", err)
return
}
if err := cmd2.Run(); err != nil {
logger.L.Printf("Failed to adjust buffer size: %v\n", err)
return
}
if err := cmd3.Run(); err != nil {
logger.L.Printf("Failed to adjust buffer size: %v\n", err)
return
}
logger.L.Printf("Thanks, QUIC-go UDP buffer size has been set to recommended value of 2048 kiB.")
}
// PrintAllResourceLimits is used to print and log all limits used by a node. First argument is the limit object, second argument is a string that describes which limit is passed (system or transient)
func PrintAllResourceLimits(l rsrcmngr.Limit, limitType string) {
logger.L.Printf("%v:\n\tMemory Limit: %v\n\tStream Limit (Inbound): %v\n\tStream Limit (Outbound): %v\n\tTotal Stream Limit: %v\n\tConn Limit (Inbound): %v\n\tConn Limit (Outbound): %v\n\tConn Total Limit: %v\n\tFile Descriptor Limit: %v\n\n", limitType, l.GetMemoryLimit(), l.GetStreamLimit(network.DirInbound), l.GetStreamLimit(network.DirOutbound), l.GetStreamTotalLimit(), l.GetConnLimit(network.DirInbound), l.GetConnLimit(network.DirOutbound), l.GetConnTotalLimit(), l.GetFDLimit())
}