github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/integration/assembly.go (about) 1 package integration 2 3 import ( 4 "crypto/ecdsa" 5 "errors" 6 "fmt" 7 "path" 8 9 "github.com/status-im/keycard-go/hexutils" 10 "github.com/syndtr/goleveldb/leveldb/opt" 11 "github.com/unicornultrafoundation/go-helios/consensus" 12 "github.com/unicornultrafoundation/go-helios/hash" 13 "github.com/unicornultrafoundation/go-helios/native/idx" 14 "github.com/unicornultrafoundation/go-helios/u2udb" 15 "github.com/unicornultrafoundation/go-helios/u2udb/multidb" 16 "github.com/unicornultrafoundation/go-u2u/accounts" 17 "github.com/unicornultrafoundation/go-u2u/accounts/keystore" 18 "github.com/unicornultrafoundation/go-u2u/cmd/utils" 19 "github.com/unicornultrafoundation/go-u2u/common" 20 "github.com/unicornultrafoundation/go-u2u/crypto" 21 "github.com/unicornultrafoundation/go-u2u/log" 22 23 "github.com/unicornultrafoundation/go-u2u/gossip" 24 "github.com/unicornultrafoundation/go-u2u/u2u/genesis" 25 "github.com/unicornultrafoundation/go-u2u/utils/adapters/vecmt2dagidx" 26 "github.com/unicornultrafoundation/go-u2u/utils/dbutil/compactdb" 27 "github.com/unicornultrafoundation/go-u2u/vecmt" 28 ) 29 30 var ( 31 MetadataPrefix = hexutils.HexToBytes("0068c2927bf842c3e9e2f1364494a33a752db334b9a819534bc9f17d2c3b4e5970008ff519d35a86f29fcaa5aae706b75dee871f65f174fcea1747f2915fc92158f6bfbf5eb79f65d16225738594bffb") 32 FlushIDKey = append(common.CopyBytes(MetadataPrefix), 0x0c) 33 TablesKey = append(common.CopyBytes(MetadataPrefix), 0x0d) 34 ) 35 36 // GenesisMismatchError is raised when trying to overwrite an existing 37 // genesis block with an incompatible one. 38 type GenesisMismatchError struct { 39 Stored, New hash.Hash 40 } 41 42 // Error implements error interface. 43 func (e *GenesisMismatchError) Error() string { 44 return fmt.Sprintf("database contains incompatible genesis (have %s, new %s)", e.Stored.String(), e.New.String()) 45 } 46 47 type Configs struct { 48 U2U gossip.Config 49 U2UStore gossip.StoreConfig 50 Hashgraph consensus.Config 51 HashgraphStore consensus.StoreConfig 52 VectorClock vecmt.IndexConfig 53 DBs DBsConfig 54 } 55 56 func panics(name string) func(error) { 57 return func(err error) { 58 log.Crit(fmt.Sprintf("%s error", name), "err", err) 59 } 60 } 61 62 func mustOpenDB(producer u2udb.DBProducer, name string) u2udb.Store { 63 db, err := producer.OpenDB(name) 64 if err != nil { 65 utils.Fatalf("Failed to open '%s' database: %v", name, err) 66 } 67 return db 68 } 69 70 func getStores(producer u2udb.FlushableDBProducer, cfg Configs) (*gossip.Store, *consensus.Store) { 71 gdb := gossip.NewStore(producer, cfg.U2UStore) 72 73 cMainDb := mustOpenDB(producer, "hashgraph") 74 cGetEpochDB := func(epoch idx.Epoch) u2udb.Store { 75 return mustOpenDB(producer, fmt.Sprintf("hashgraph-%d", epoch)) 76 } 77 cdb := consensus.NewStore(cMainDb, cGetEpochDB, panics("Hashgraph store"), cfg.HashgraphStore) 78 return gdb, cdb 79 } 80 81 func getEpoch(producer u2udb.FlushableDBProducer, cfg Configs) idx.Epoch { 82 gdb := gossip.NewStore(producer, cfg.U2UStore) 83 defer gdb.Close() 84 return gdb.GetEpoch() 85 } 86 87 func rawApplyGenesis(gdb *gossip.Store, cdb *consensus.Store, g genesis.Genesis, cfg Configs) error { 88 _, _, _, err := rawMakeEngine(gdb, cdb, &g, cfg) 89 return err 90 } 91 92 func rawMakeEngine(gdb *gossip.Store, cdb *consensus.Store, g *genesis.Genesis, cfg Configs) (*consensus.Consensus, *vecmt.Index, gossip.BlockProc, error) { 93 blockProc := gossip.DefaultBlockProc() 94 95 if g != nil { 96 _, err := gdb.ApplyGenesis(*g) 97 if err != nil { 98 return nil, nil, blockProc, fmt.Errorf("failed to write Gossip genesis state: %v", err) 99 } 100 101 err = cdb.ApplyGenesis(&consensus.Genesis{ 102 Epoch: gdb.GetEpoch(), 103 Validators: gdb.GetValidators(), 104 }) 105 if err != nil { 106 return nil, nil, blockProc, fmt.Errorf("failed to write Hashgraph genesis state: %v", err) 107 } 108 } 109 110 // create consensus 111 vecClock := vecmt.NewIndex(panics("Vector clock"), cfg.VectorClock) 112 engine := consensus.NewConsensus(cdb, &GossipStoreAdapter{gdb}, vecmt2dagidx.Wrap(vecClock), panics("Hashgraph"), cfg.Hashgraph) 113 return engine, vecClock, blockProc, nil 114 } 115 116 func applyGenesis(dbs u2udb.FlushableDBProducer, g genesis.Genesis, cfg Configs) error { 117 gdb, cdb := getStores(dbs, cfg) 118 defer gdb.Close() 119 defer cdb.Close() 120 log.Info("Applying genesis state") 121 err := rawApplyGenesis(gdb, cdb, g, cfg) 122 if err != nil { 123 return err 124 } 125 err = gdb.Commit() 126 if err != nil { 127 return err 128 } 129 return nil 130 } 131 132 func migrate(dbs u2udb.FlushableDBProducer, cfg Configs) error { 133 gdb, cdb := getStores(dbs, cfg) 134 defer gdb.Close() 135 defer cdb.Close() 136 err := gdb.Commit() 137 if err != nil { 138 return err 139 } 140 return nil 141 } 142 143 func CheckStateInitialized(chaindataDir string, cfg DBsConfig) error { 144 if isInterrupted(chaindataDir) { 145 return errors.New("genesis processing isn't finished") 146 } 147 runtimeProducers, runtimeScopedProducers := SupportedDBs(chaindataDir, cfg.RuntimeCache) 148 dbs, err := MakeMultiProducer(runtimeProducers, runtimeScopedProducers, cfg.Routing) 149 if err != nil { 150 return err 151 } 152 return dbs.Close() 153 } 154 155 func compactDB(typ multidb.TypeName, name string, producer u2udb.DBProducer) error { 156 humanName := path.Join(string(typ), name) 157 db, err := producer.OpenDB(name) 158 defer db.Close() 159 if err != nil { 160 return err 161 } 162 return compactdb.Compact(db, humanName, 16*opt.GiB) 163 } 164 165 func makeEngine(chaindataDir string, g *genesis.Genesis, genesisProc bool, cfg Configs) (*consensus.Consensus, *vecmt.Index, *gossip.Store, *consensus.Store, gossip.BlockProc, func() error, error) { 166 // Genesis processing 167 if genesisProc { 168 setGenesisProcessing(chaindataDir) 169 // use increased DB cache for genesis processing 170 genesisProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.GenesisCache) 171 if g == nil { 172 return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("missing --genesis flag for an empty datadir") 173 } 174 dbs, err := MakeDirectMultiProducer(genesisProducers, cfg.DBs.Routing) 175 if err != nil { 176 return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("failed to make DB multi-producer: %v", err) 177 } 178 err = applyGenesis(dbs, *g, cfg) 179 if err != nil { 180 _ = dbs.Close() 181 return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("failed to apply genesis state: %v", err) 182 } 183 _ = dbs.Close() 184 setGenesisComplete(chaindataDir) 185 } 186 // Compact DBs after first launch 187 if genesisProc { 188 genesisProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.GenesisCache) 189 for typ, p := range genesisProducers { 190 for _, name := range p.Names() { 191 if err := compactDB(typ, name, p); err != nil { 192 return nil, nil, nil, nil, gossip.BlockProc{}, nil, err 193 } 194 } 195 } 196 } 197 // Check DBs are synced 198 { 199 err := CheckStateInitialized(chaindataDir, cfg.DBs) 200 if err != nil { 201 return nil, nil, nil, nil, gossip.BlockProc{}, nil, err 202 } 203 } 204 // Migration 205 { 206 runtimeProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.RuntimeCache) 207 dbs, err := MakeDirectMultiProducer(runtimeProducers, cfg.DBs.Routing) 208 if err != nil { 209 return nil, nil, nil, nil, gossip.BlockProc{}, nil, err 210 } 211 212 // drop previous epoch DBs, which do not survive restart 213 epoch := getEpoch(dbs, cfg) 214 leDB, err := dbs.OpenDB(fmt.Sprintf("hashgraph-%d", epoch)) 215 if err != nil { 216 _ = dbs.Close() 217 return nil, nil, nil, nil, gossip.BlockProc{}, nil, err 218 } 219 _ = leDB.Close() 220 leDB.Drop() 221 goDB, err := dbs.OpenDB(fmt.Sprintf("gossip-%d", epoch)) 222 if err != nil { 223 _ = dbs.Close() 224 return nil, nil, nil, nil, gossip.BlockProc{}, nil, err 225 } 226 _ = goDB.Close() 227 goDB.Drop() 228 229 err = migrate(dbs, cfg) 230 _ = dbs.Close() 231 if err != nil { 232 return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("failed to migrate state: %v", err) 233 } 234 } 235 // Live setup 236 237 runtimeProducers, runtimeScopedProducers := SupportedDBs(chaindataDir, cfg.DBs.RuntimeCache) 238 // open flushable DBs 239 dbs, err := MakeMultiProducer(runtimeProducers, runtimeScopedProducers, cfg.DBs.Routing) 240 if err != nil { 241 return nil, nil, nil, nil, gossip.BlockProc{}, nil, err 242 } 243 244 gdb, cdb := getStores(dbs, cfg) 245 defer func() { 246 if err != nil { 247 gdb.Close() 248 cdb.Close() 249 dbs.Close() 250 } 251 }() 252 253 // compare genesis with the input 254 genesisID := gdb.GetGenesisID() 255 if genesisID == nil { 256 err = errors.New("malformed chainstore: genesis ID is not written") 257 return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err 258 } 259 if g != nil { 260 if *genesisID != g.GenesisID { 261 err = &GenesisMismatchError{*genesisID, g.GenesisID} 262 return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err 263 } 264 } 265 266 engine, vecClock, blockProc, err := rawMakeEngine(gdb, cdb, nil, cfg) 267 if err != nil { 268 err = fmt.Errorf("failed to make engine: %v", err) 269 return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err 270 } 271 272 if genesisProc { 273 err = gdb.Commit() 274 if err != nil { 275 err = fmt.Errorf("failed to commit DBs: %v", err) 276 return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err 277 } 278 } 279 280 return engine, vecClock, gdb, cdb, blockProc, dbs.Close, nil 281 } 282 283 // MakeEngine makes consensus engine from config. 284 func MakeEngine(chaindataDir string, g *genesis.Genesis, cfg Configs) (*consensus.Consensus, *vecmt.Index, *gossip.Store, *consensus.Store, gossip.BlockProc, func() error) { 285 // Legacy DBs migrate 286 if cfg.DBs.MigrationMode != "reformat" && cfg.DBs.MigrationMode != "rebuild" && cfg.DBs.MigrationMode != "" { 287 utils.Fatalf("MigrationMode must be 'reformat' or 'rebuild'") 288 } 289 if !isEmpty(path.Join(chaindataDir, "gossip")) { 290 MakeDBDirs(chaindataDir) 291 genesisProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.GenesisCache) 292 dbs, err := MakeDirectMultiProducer(genesisProducers, cfg.DBs.Routing) 293 if err != nil { 294 utils.Fatalf("Failed to make engine: %v", err) 295 } 296 err = migrateLegacyDBs(chaindataDir, dbs, cfg.DBs.MigrationMode, cfg.DBs.Routing) 297 _ = dbs.Close() 298 if err != nil { 299 utils.Fatalf("Failed to migrate state: %v", err) 300 } 301 } 302 303 dropAllDBsIfInterrupted(chaindataDir) 304 firstLaunch := isEmpty(chaindataDir) 305 MakeDBDirs(chaindataDir) 306 307 engine, vecClock, gdb, cdb, blockProc, closeDBs, err := makeEngine(chaindataDir, g, firstLaunch, cfg) 308 if err != nil { 309 if firstLaunch { 310 dropAllDBs(chaindataDir) 311 } 312 utils.Fatalf("Failed to make engine: %v", err) 313 } 314 315 rules := gdb.GetRules() 316 genesisID := gdb.GetGenesisID() 317 if firstLaunch { 318 log.Info("Applied genesis state", "name", rules.Name, "id", rules.NetworkID, "genesis", genesisID.String()) 319 } else { 320 log.Info("Genesis is already written", "name", rules.Name, "id", rules.NetworkID, "genesis", genesisID.String()) 321 } 322 323 return engine, vecClock, gdb, cdb, blockProc, closeDBs 324 } 325 326 // SetAccountKey sets key into accounts manager and unlocks it with pswd. 327 func SetAccountKey( 328 am *accounts.Manager, key *ecdsa.PrivateKey, pswd string, 329 ) ( 330 acc accounts.Account, 331 ) { 332 kss := am.Backends(keystore.KeyStoreType) 333 if len(kss) < 1 { 334 log.Crit("Keystore is not found") 335 return 336 } 337 ks := kss[0].(*keystore.KeyStore) 338 339 acc = accounts.Account{ 340 Address: crypto.PubkeyToAddress(key.PublicKey), 341 } 342 343 imported, err := ks.ImportECDSA(key, pswd) 344 if err == nil { 345 acc = imported 346 } else if err.Error() != "account already exists" { 347 log.Crit("Failed to import key", "err", err) 348 } 349 350 err = ks.Unlock(acc, pswd) 351 if err != nil { 352 log.Crit("failed to unlock key", "err", err) 353 } 354 355 return 356 }