github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/service.go (about) 1 // Copyright (C) 2013-2017, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.) 2 // Use of this source code is governed by GPLv3 found in the LICENSE file 3 //---------------------------------------------------------------------------------------- 4 // Service implements functions and data that provide Holochain services in a unix file based environment 5 6 package holochain 7 8 import ( 9 "bytes" 10 "encoding/base64" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "github.com/BurntSushi/toml" 15 "github.com/google/uuid" 16 . "github.com/holochain/holochain-proto/hash" 17 "io" 18 "io/ioutil" 19 "net/http" 20 "os" 21 "path/filepath" 22 "regexp" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 ) 28 29 // System settings, directory, and file names 30 const ( 31 DefaultDirectoryName string = ".holochain" // Directory for storing config data 32 ChainDataDir string = "db" // Sub-directory for all chain content files 33 ChainDNADir string = "dna" // Sub-directory for all chain definition files 34 ChainUIDir string = "ui" // Sub-directory for all chain user interface files 35 ChainTestDir string = "test" // Sub-directory for all chain test files 36 DNAFileName string = "dna" // Definition of the Holochain 37 ConfigFileName string = "config" // Settings of the Holochain 38 SysFileName string = "system.conf" // Server & System settings 39 AgentFileName string = "agent.txt" // User ID info 40 PrivKeyFileName string = "priv.key" // Signing key - private 41 StoreFileName string = "chain.db" // Filename for local data store 42 DNAHashFileName string = "dna.hash" // Filename for storing the hash of the holochain 43 DHTStoreFileName string = "dht.db" // Filname for storing the dht 44 BridgeDBFileName string = "bridge.db" // Filname for storing bridge keys 45 46 TestConfigFileName string = "_config.json" 47 48 DefaultDHTPort = 6283 49 DefaultBootstrapServer = "bootstrap.holochain.net:10000" 50 51 DefaultHashType HashType = HashType("sha2-256") // default hashing algo if not provided in DNA 52 53 CloneWithNewUUID = true 54 CloneWithSameUUID = false 55 InitializeDB = true 56 SkipInitializeDB = false 57 ) 58 59 // 60 type CloneSpec struct { 61 Role string 62 Number int 63 } 64 65 // TestConfig holds the configuration options for a test 66 type TestConfig struct { 67 GossipInterval int // interval in milliseconds between gossips 68 Duration int // if non-zero number of seconds to keep all nodes alive 69 Clone []CloneSpec 70 } 71 72 // ServiceConfig holds the service settings 73 type ServiceConfig struct { 74 DefaultPeerModeAuthor bool 75 DefaultPeerModeDHTNode bool 76 DefaultBootstrapServer string 77 DefaultEnableMDNS bool 78 DefaultEnableNATUPnP bool 79 } 80 81 // A Service is a Holochain service data structure 82 type Service struct { 83 Settings ServiceConfig 84 DefaultAgent Agent 85 Path string 86 } 87 88 type EntryDefFile struct { 89 Name string 90 DataFormat string 91 Schema string 92 SchemaFile string // file name of schema or language schema directive 93 Sharing string 94 } 95 96 type ZomeFile struct { 97 Name string 98 Description string 99 CodeFile string 100 RibosomeType string 101 BridgeFuncs []string // functions in zome that can be bridged to by fromApp 102 Config map[string]interface{} 103 Entries []EntryDefFile 104 Functions []FunctionDef 105 } 106 107 type DNAFile struct { 108 Version int 109 UUID uuid.UUID 110 Name string 111 Properties map[string]string 112 PropertiesSchemaFile string 113 BasedOn Hash // references hash of another holochain that these schemas and code are derived from 114 RequiresVersion int 115 DHTConfig DHTConfig 116 Progenitor Progenitor 117 Zomes []ZomeFile 118 } 119 120 // AgentFixture defines an agent for the purposes of tests 121 type AgentFixture struct { 122 Hash string 123 Identity string 124 } 125 126 // TestFixtures defines data needed to run tests 127 type TestFixtures struct { 128 Agents []AgentFixture 129 } 130 131 // TestSet holds a set of tests plus configuration and fixture data for those tests 132 type TestSet struct { 133 Tests []TestData 134 Identity string 135 Fixtures TestFixtures 136 Benchmark bool // activate benchmarking for all tests 137 } 138 139 // TestData holds a test entry for a chain 140 type TestData struct { 141 Convey string // a human readable description of the tests intent 142 Zome string // the zome in which to find the function 143 FnName string // the function to call 144 Input interface{} // the function's input 145 Output interface{} // the expected output to match against (full match) 146 Err interface{} // the expected error to match against 147 ErrMsg string // the expected error message to match against 148 Regexp string // the expected out to match again (regular expression) 149 Time time.Duration // offset in milliseconds from the start of the test at which to run this test. 150 Wait time.Duration // time in milliseconds to wait before running this test from when the previous ran 151 Exposure string // the exposure context for the test call (defaults to ZOME_EXPOSURE) 152 Raw bool // set to true if we should ignore fnName and just call input as raw code in the zome, useful for testing helper functions and validation functions 153 Repeat int // number of times to repeat this test, useful for scenario testing 154 Benchmark bool // activate benchmarking for this test 155 } 156 157 // IsInitialized checks a path for a correctly set up .holochain directory 158 func IsInitialized(root string) bool { 159 return DirExists(root) && FileExists(filepath.Join(root, SysFileName)) && FileExists(filepath.Join(root, AgentFileName)) 160 } 161 162 // Init initializes service defaults including a signing key pair for an agent 163 // and writes them out to configuration files in the root path (making the 164 // directory if necessary) 165 func Init(root string, identity AgentIdentity, seed io.Reader) (service *Service, err error) { 166 //TODO this is in the wrong place it should be where HeadersEntryDef gets initialized 167 if HeadersEntryDef.validator == nil { 168 err = HeadersEntryDef.BuildJSONSchemaValidatorFromString(HeadersEntryDef.Schema) 169 } 170 if AgentEntryDef.validator == nil { 171 err = AgentEntryDef.BuildJSONSchemaValidatorFromString(AgentEntryDef.Schema) 172 } 173 if DelEntryDef.validator == nil { 174 err = DelEntryDef.BuildJSONSchemaValidatorFromString(DelEntryDef.Schema) 175 } 176 if MigrateEntryDef.validator == nil { 177 err = MigrateEntryDef.BuildJSONSchemaValidatorFromString(MigrateEntryDef.Schema) 178 } 179 if err != nil { 180 return 181 } 182 183 err = os.MkdirAll(root, os.ModePerm) 184 if err != nil { 185 return 186 } 187 s := Service{ 188 Settings: ServiceConfig{ 189 DefaultPeerModeDHTNode: true, 190 DefaultPeerModeAuthor: true, 191 DefaultBootstrapServer: DefaultBootstrapServer, 192 DefaultEnableMDNS: true, 193 DefaultEnableNATUPnP: true, 194 }, 195 Path: root, 196 } 197 198 var val string 199 200 val = os.Getenv("HC_DEFAULT_BOOTSTRAPSERVER") 201 if val != "" { 202 s.Settings.DefaultBootstrapServer = val 203 Infof("Using HC_DEFAULT_BOOTSTRAPSERVER--configuring default bootstrap server as: %s\n", s.Settings.DefaultBootstrapServer) 204 } 205 206 val = os.Getenv("HC_DEFAULT_ENABLEMDNS") 207 if val != "" { 208 s.Settings.DefaultEnableMDNS = val == "true" 209 Infof("Using HC_DEFAULT_ENABLEMDNS--configuring default MDNS use as: %v.\n", s.Settings.DefaultEnableMDNS) 210 } 211 212 val = os.Getenv("HC_DEFAULT_ENABLENATUPNP") 213 if val != "" { 214 s.Settings.DefaultEnableNATUPnP = val == "true" 215 Infof("Using HC_DEFAULT_ENABLENATUPNP--configuring default UPnP use as: %v.\n", s.Settings.DefaultEnableNATUPnP) 216 } 217 218 err = writeToml(root, SysFileName, s.Settings, false) 219 if err != nil { 220 return 221 } 222 223 a, err := NewAgent(LibP2P, identity, seed) 224 if err != nil { 225 return 226 } 227 err = SaveAgent(root, a) 228 if err != nil { 229 return 230 } 231 232 s.DefaultAgent = a 233 234 service = &s 235 return 236 } 237 238 // LoadService creates a service object from a configuration file 239 func LoadService(path string) (service *Service, err error) { 240 agent, err := LoadAgent(path) 241 if err != nil { 242 return 243 } 244 s := Service{ 245 Path: path, 246 DefaultAgent: agent, 247 } 248 249 _, err = toml.DecodeFile(filepath.Join(path, SysFileName), &s.Settings) 250 if err != nil { 251 return 252 } 253 254 if err = s.Settings.Validate(); err != nil { 255 return 256 } 257 258 service = &s 259 return 260 } 261 262 // Validate validates settings values 263 func (c *ServiceConfig) Validate() (err error) { 264 if !(c.DefaultPeerModeAuthor || c.DefaultPeerModeDHTNode) { 265 err = errors.New(SysFileName + ": At least one peer mode must be set to true.") 266 return 267 } 268 return 269 } 270 271 // ConfiguredChains returns a list of the configured chains for the given service 272 func (s *Service) ConfiguredChains() (chains map[string]*Holochain, err error) { 273 files, err := ioutil.ReadDir(s.Path) 274 if err != nil { 275 return 276 } 277 chains = make(map[string]*Holochain) 278 for _, f := range files { 279 if f.IsDir() { 280 h, err := s.Load(f.Name()) 281 if err == nil { 282 chains[f.Name()] = h 283 } 284 } 285 } 286 return 287 } 288 289 // Find the DNA files 290 func findDNA(path string) (f string, err error) { 291 p := filepath.Join(path, DNAFileName) 292 293 matches, err := filepath.Glob(p + ".*") 294 if err != nil { 295 return 296 } 297 for _, fn := range matches { 298 f = EncodingFormat(fn) 299 if f != "" { 300 break 301 } 302 } 303 304 if f == "" { 305 err = fmt.Errorf("No DNA file in %s%s", path, string(os.PathSeparator)) 306 return 307 } 308 return 309 } 310 311 // IsConfigured checks a directory for correctly set up holochain configuration file 312 func (s *Service) IsConfigured(name string) (f string, err error) { 313 root := filepath.Join(s.Path, name) 314 315 f, err = findDNA(filepath.Join(root, ChainDNADir)) 316 if err != nil { 317 return 318 } 319 //@todo check other things? 320 321 return 322 } 323 324 // Load instantiates a Holochain instance from disk 325 func (s *Service) Load(name string) (h *Holochain, err error) { 326 f, err := s.IsConfigured(name) 327 if err != nil { 328 return 329 } 330 h, err = s.load(name, f) 331 return 332 } 333 334 // loadDNA decodes a DNA from a directory hierarchy as specified by a DNAFile 335 func (s *Service) loadDNA(path string, filename string, format string) (dnaP *DNA, err error) { 336 var dnaFile DNAFile 337 var dna DNA 338 dnafile := filepath.Join(path, filename+"."+format) 339 f, err := os.Open(dnafile) 340 if err != nil { 341 err = fmt.Errorf("error opening DNA file %s: %v", dnafile, err) 342 return 343 } 344 defer f.Close() 345 346 err = Decode(f, format, &dnaFile) 347 if err != nil { 348 err = fmt.Errorf("error decoding DNA file %s: %v", dnafile, err) 349 return 350 } 351 352 var validator SchemaValidator 353 var propertiesSchema []byte 354 if dnaFile.PropertiesSchemaFile != "" { 355 propertiesSchema, err = ReadFile(path, dnaFile.PropertiesSchemaFile) 356 if err != nil { 357 err = fmt.Errorf("error reading properties Schema file %s: %v", dnaFile.PropertiesSchemaFile, err) 358 return 359 } 360 schemapath := filepath.Join(path, dnaFile.PropertiesSchemaFile) 361 validator, err = BuildJSONSchemaValidatorFromFile(schemapath) 362 if err != nil { 363 err = fmt.Errorf("error building validator for %s: %v", schemapath, err) 364 return 365 } 366 } 367 368 dna.Version = dnaFile.Version 369 dna.UUID = dnaFile.UUID 370 dna.Name = dnaFile.Name 371 dna.BasedOn = dnaFile.BasedOn 372 dna.RequiresVersion = dnaFile.RequiresVersion 373 dna.DHTConfig = dnaFile.DHTConfig 374 dna.Progenitor = dnaFile.Progenitor 375 dna.Properties = dnaFile.Properties 376 dna.PropertiesSchema = string(propertiesSchema) 377 dna.propertiesSchemaValidator = validator 378 379 // Fallback to the default hash type 380 var emptyHashType HashType 381 if dna.DHTConfig.HashType == emptyHashType { 382 dna.DHTConfig.HashType = DefaultHashType 383 } 384 385 err = dna.check() 386 if err != nil { 387 err = fmt.Errorf("dna failed check with: %v", err) 388 return 389 } 390 391 dna.Zomes = make([]Zome, len(dnaFile.Zomes)) 392 for i, zome := range dnaFile.Zomes { 393 if zome.CodeFile == "" { 394 var ext string 395 switch zome.RibosomeType { 396 case "js": 397 ext = ".js" 398 case "zygo": 399 ext = ".zy" 400 } 401 dnaFile.Zomes[i].CodeFile = zome.Name + ext 402 } 403 404 zomePath := filepath.Join(path, zome.Name) 405 codeFilePath := filepath.Join(zomePath, zome.CodeFile) 406 if !FileExists(codeFilePath) { 407 return nil, errors.New("DNA specified code file missing: " + zome.CodeFile) 408 } 409 410 dna.Zomes[i].Name = zome.Name 411 dna.Zomes[i].Description = zome.Description 412 dna.Zomes[i].RibosomeType = zome.RibosomeType 413 dna.Zomes[i].Functions = zome.Functions 414 dna.Zomes[i].Config = zome.Config 415 dna.Zomes[i].BridgeFuncs = zome.BridgeFuncs 416 417 var code []byte 418 code, err = ReadFile(zomePath, zome.CodeFile) 419 if err != nil { 420 return 421 } 422 dna.Zomes[i].Code = string(code[:]) 423 424 dna.Zomes[i].Entries = make([]EntryDef, len(zome.Entries)) 425 for j, entry := range zome.Entries { 426 dna.Zomes[i].Entries[j].Name = entry.Name 427 dna.Zomes[i].Entries[j].DataFormat = entry.DataFormat 428 dna.Zomes[i].Entries[j].Sharing = entry.Sharing 429 dna.Zomes[i].Entries[j].Schema = entry.Schema 430 if entry.Schema == "" && entry.SchemaFile != "" { 431 schemaFilePath := filepath.Join(zomePath, entry.SchemaFile) 432 if !FileExists(schemaFilePath) { 433 return nil, errors.New("DNA specified schema file missing: " + schemaFilePath) 434 } 435 var schema []byte 436 schema, err = ReadFile(zomePath, entry.SchemaFile) 437 if err != nil { 438 return 439 } 440 dna.Zomes[i].Entries[j].Schema = string(schema) 441 if strings.HasSuffix(entry.SchemaFile, ".json") { 442 if err = dna.Zomes[i].Entries[j].BuildJSONSchemaValidator(schemaFilePath); err != nil { 443 err = fmt.Errorf("error building validator for %s: %v", schemaFilePath, err) 444 return nil, err 445 } 446 } 447 } 448 } 449 } 450 451 dnaP = &dna 452 return 453 } 454 455 // load unmarshals a holochain structure for the named chain and format 456 func (s *Service) load(name string, format string) (hP *Holochain, err error) { 457 var h Holochain 458 root := filepath.Join(s.Path, name) 459 460 // load the config 461 var f *os.File 462 f, err = os.Open(filepath.Join(root, ConfigFileName+"."+format)) 463 if err != nil { 464 return 465 } 466 defer f.Close() 467 err = Decode(f, format, &h.Config) 468 if err != nil { 469 return 470 } 471 if err = h.Config.Setup(); err != nil { 472 return 473 } 474 475 dna, err := s.loadDNA(filepath.Join(root, ChainDNADir), DNAFileName, format) 476 if err != nil { 477 return 478 } 479 480 h.encodingFormat = format 481 h.rootPath = root 482 h.nucleus = NewNucleus(&h, dna) 483 484 // try and get the holochain-specific agent info 485 agent, err := LoadAgent(root) 486 if err != nil { 487 // if not specified for this app, get the default from the Agent.txt file for all apps 488 agent, err = LoadAgent(filepath.Dir(root)) 489 } 490 491 // TODO verify Agent identity against schema 492 if err != nil { 493 return 494 } 495 h.agent = agent 496 497 // once the agent is set up we can calculate the id 498 h.nodeID, h.nodeIDStr, err = agent.NodeID() 499 if err != nil { 500 return 501 } 502 503 if err = h.PrepareHashType(); err != nil { 504 return 505 } 506 507 h.chain, err = NewChainFromFile(h.hashSpec, filepath.Join(h.DBPath(), StoreFileName)) 508 if err != nil { 509 return 510 } 511 512 // if the chain has been started there should be a DNAHashFile which 513 // we can load to check against the actual hash of the DNA entry 514 if len(h.chain.Headers) > 0 { 515 h.dnaHash = h.chain.Headers[0].EntryLink.Clone() 516 517 var b []byte 518 b, err = ReadFile(h.rootPath, DNAHashFileName) 519 if err == nil { 520 if h.dnaHash.String() != string(b) { 521 err = errors.New("DNA doesn't match file!") 522 return 523 } 524 } 525 } 526 527 // @TODO compare value from file to actual hash 528 529 if h.chain.Length() > 0 { 530 h.agentHash = h.chain.Headers[1].EntryLink 531 _, topHeader := h.chain.TopType(AgentEntryType) 532 h.agentTopHash = topHeader.EntryLink 533 } 534 hP = &h 535 return 536 } 537 538 // gen calls a make function which should build the holochain structure and supporting files 539 func gen(root string, initDB bool, makeH func(root string) (hP *Holochain, err error)) (h *Holochain, err error) { 540 if DirExists(root) { 541 return nil, mkErr(root + " already exists") 542 } 543 if err := os.MkdirAll(root, os.ModePerm); err != nil { 544 return nil, err 545 } 546 547 // cleanup the directory if we enounter an error while generating 548 defer func() { 549 if err != nil { 550 os.RemoveAll(root) 551 } 552 }() 553 554 h, err = makeH(root) 555 if err != nil { 556 return nil, err 557 } 558 559 if initDB { 560 if err := os.MkdirAll(h.DBPath(), os.ModePerm); err != nil { 561 return nil, err 562 } 563 564 h.chain, err = NewChainFromFile(h.hashSpec, filepath.Join(h.DBPath(), StoreFileName)) 565 if err != nil { 566 return nil, err 567 } 568 } 569 return 570 } 571 572 func suffixByRibosomeType(ribosomeType string) (suffix string) { 573 switch ribosomeType { 574 case JSRibosomeType: 575 suffix = ".js" 576 case ZygoRibosomeType: 577 suffix = ".zy" 578 default: 579 } 580 return 581 } 582 583 func _makeConfig(s *Service) (config Config, err error) { 584 config = Config{ 585 DHTPort: DefaultDHTPort, 586 PeerModeDHTNode: s.Settings.DefaultPeerModeDHTNode, 587 PeerModeAuthor: s.Settings.DefaultPeerModeAuthor, 588 BootstrapServer: s.Settings.DefaultBootstrapServer, 589 EnableNATUPnP: s.Settings.DefaultEnableNATUPnP, 590 EnableMDNS: s.Settings.DefaultEnableMDNS, 591 Loggers: Loggers{ 592 Debug: Logger{Name: "Debug", Format: "HC: %{file}.%{line}: %{message}", Enabled: false}, 593 App: Logger{Name: "App", Format: "%{color:cyan}%{message}", Enabled: false}, 594 DHT: Logger{Name: "DHT", Format: "%{color:yellow}%{time} DHT: %{message}"}, 595 Gossip: Logger{Name: "Gossip", Format: "%{color:blue}%{time} Gossip: %{message}"}, 596 TestPassed: Logger{Name: "TestPassed", Format: "%{color:green}%{message}", Enabled: true}, 597 TestFailed: Logger{Name: "TestFailed", Format: "%{color:red}%{message}", Enabled: true}, 598 TestInfo: Logger{Name: "TestInfo", Format: "%{message}", Enabled: true}, 599 }, 600 } 601 602 val := os.Getenv("HOLOCHAINCONFIG_DHTPORT") 603 if val != "" { 604 Debugf("makeConfig: using environment variable to set port to: %s", val) 605 config.DHTPort, err = strconv.Atoi(val) 606 if err != nil { 607 return 608 } 609 Debugf("makeConfig: using environment variable to set port to: %v\n", val) 610 } 611 val = os.Getenv("HOLOCHAINCONFIG_BOOTSTRAP") 612 if val != "" { 613 if val == "_" { 614 val = "" 615 } 616 config.BootstrapServer = val 617 if val == "" { 618 val = "NO BOOTSTRAP SERVER" 619 } 620 Debugf("makeConfig: using environment variable to set bootstrap server to: %s", val) 621 } 622 623 val = os.Getenv("HOLOCHAINCONFIG_ENABLEMDNS") 624 if val != "" { 625 Debugf("makeConfig: using environment variable to set enableMDNS to: %s", val) 626 config.EnableMDNS = val == "true" 627 } 628 629 val = os.Getenv("HOLOCHAINCONFIG_ENABLENATUPNP") 630 if val != "" { 631 Debugf("makeConfig: using environment variable to set enableNATUPnP to: %s", val) 632 config.EnableNATUPnP = val == "true" 633 } 634 return 635 } 636 637 func makeConfig(h *Holochain, s *Service) (err error) { 638 h.Config, err = _makeConfig(s) 639 if err != nil { 640 return 641 } 642 p := filepath.Join(h.rootPath, ConfigFileName+"."+h.encodingFormat) 643 f, err := os.Create(p) 644 if err != nil { 645 return err 646 } 647 defer f.Close() 648 649 if err = Encode(f, h.encodingFormat, &h.Config); err != nil { 650 return 651 } 652 if err = h.Config.Setup(); err != nil { 653 return 654 } 655 return 656 } 657 658 func (service *Service) InitAppDir(root string, encodingFormat string) (err error) { 659 var config Config 660 config, err = _makeConfig(service) 661 if err != nil { 662 return 663 } 664 p := filepath.Join(root, ConfigFileName+"."+encodingFormat) 665 var f, f1 *os.File 666 f, err = os.Create(p) 667 if err != nil { 668 return 669 } 670 defer f.Close() 671 672 if err = Encode(f, encodingFormat, &config); err != nil { 673 return 674 } 675 676 if err = os.MkdirAll(filepath.Join(root, ChainDataDir), os.ModePerm); err != nil { 677 return 678 } 679 680 f1, err = os.Create(filepath.Join(root, ChainDataDir, StoreFileName)) 681 if err != nil { 682 return 683 } 684 defer f1.Close() 685 return 686 } 687 688 // MakeTestingApp generates a holochain used for testing purposes 689 func (s *Service) MakeTestingApp(root string, encodingFormat string, initDB bool, newUUID bool, agent Agent) (h *Holochain, err error) { 690 if DirExists(root) { 691 return nil, mkErr(root + " already exists") 692 } 693 appPackageReader := bytes.NewBuffer([]byte(TestingAppAppPackage())) 694 695 if filepath.IsAbs(root) { 696 origPath := s.Path 697 s.Path = filepath.Dir(root) 698 defer func() { s.Path = origPath }() 699 } 700 701 name := filepath.Base(root) 702 _, err = s.SaveFromAppPackage(appPackageReader, root, "test", agent, TestingAppDecodingFormat, encodingFormat, newUUID) 703 if err != nil { 704 return 705 } 706 if err = mkChainDirs(root, initDB); err != nil { 707 return 708 } 709 710 if initDB { 711 var config Config 712 config, err = _makeConfig(s) 713 if err != nil { 714 return 715 } 716 p := filepath.Join(root, ConfigFileName+"."+encodingFormat) 717 var f, f1 *os.File 718 f, err = os.Create(p) 719 if err != nil { 720 return 721 } 722 defer f.Close() 723 724 if err = Encode(f, encodingFormat, &config); err != nil { 725 return 726 } 727 728 f1, err = os.Create(filepath.Join(root, ChainDataDir, StoreFileName)) 729 if err != nil { 730 return 731 } 732 defer f1.Close() 733 734 h, err = s.Load(name) 735 if err != nil { 736 return 737 } 738 if err = h.Config.Setup(); err != nil { 739 return 740 } 741 } 742 return 743 } 744 745 // if the directories don't exist, make the place to store chains 746 func mkChainDirs(root string, initDB bool) (err error) { 747 if initDB { 748 if err = os.MkdirAll(filepath.Join(root, ChainDataDir), os.ModePerm); err != nil { 749 return err 750 } 751 } 752 if err = os.MkdirAll(filepath.Join(root, ChainUIDir), os.ModePerm); err != nil { 753 return 754 } 755 if err = os.MkdirAll(filepath.Join(root, ChainTestDir), os.ModePerm); err != nil { 756 return 757 } 758 return 759 } 760 761 // Clone copies DNA files from a source directory 762 // bool new indicates if this clone should create a new DNA (when true) or act as a Join 763 func (s *Service) Clone(srcPath string, root string, agent Agent, new bool, initDB bool) (hP *Holochain, err error) { 764 hP, err = gen(root, initDB, func(root string) (*Holochain, error) { 765 var h Holochain 766 srcDNAPath := filepath.Join(srcPath, ChainDNADir) 767 768 format, err := findDNA(srcDNAPath) 769 if err != nil { 770 return nil, err 771 } 772 773 dna, err := s.loadDNA(srcDNAPath, DNAFileName, format) 774 if err != nil { 775 return nil, err 776 } 777 778 h.nucleus = NewNucleus(&h, dna) 779 h.encodingFormat = format 780 h.rootPath = root 781 782 // create the DNA directory and copy 783 if err := os.MkdirAll(h.DNAPath(), os.ModePerm); err != nil { 784 return nil, err 785 } 786 787 // TODO verify identity against schema? 788 h.agent = agent 789 790 // once the agent is set up we can calculate the id 791 h.nodeID, h.nodeIDStr, err = agent.NodeID() 792 if err != nil { 793 return nil, err 794 } 795 796 // make a config file 797 if err = makeConfig(&h, s); err != nil { 798 return nil, err 799 } 800 801 if new { 802 h.nucleus.dna.NewUUID() 803 804 // use the path as the name 805 h.nucleus.dna.Name = filepath.Base(root) 806 807 // change the progenitor to self because this is a clone! 808 pk, err := agent.PubKey().Bytes() 809 if err != nil { 810 return nil, err 811 } 812 h.nucleus.dna.Progenitor = Progenitor{Identity: string(agent.Identity()), PubKey: pk} 813 } 814 815 // save out the DNA file 816 if err = s.saveDNAFile(h.rootPath, h.nucleus.dna, h.encodingFormat, true); err != nil { 817 return nil, err 818 } 819 820 // and the agent 821 err = SaveAgent(h.rootPath, h.agent) 822 if err != nil { 823 return nil, err 824 } 825 826 // copy any UI files 827 srcUIPath := filepath.Join(srcPath, ChainUIDir) 828 if DirExists(srcUIPath) { 829 if err = CopyDir(srcUIPath, h.UIPath()); err != nil { 830 return nil, err 831 } 832 } 833 834 // copy any test files 835 srcTestDir := filepath.Join(srcPath, ChainTestDir) 836 if DirExists(srcTestDir) { 837 if err = CopyDir(srcTestDir, filepath.Join(root, ChainTestDir)); err != nil { 838 return nil, err 839 } 840 } 841 return &h, nil 842 }) 843 return 844 } 845 846 func DNAHashofUngenedChain(h *Holochain) (DNAHash Hash, err error) { 847 var buf bytes.Buffer 848 849 err = h.EncodeDNA(&buf) 850 e := GobEntry{C: buf.Bytes()} 851 852 err = h.PrepareHashType() 853 if err != nil { 854 return 855 } 856 857 var dnaHeader *Header 858 _, dnaHeader, err = newHeader(h.hashSpec, time.Now(), DNAEntryType, &e, h.agent.PrivKey(), NullHash(), NullHash(), NullHash()) 859 if err != nil { 860 return 861 } 862 DNAHash = dnaHeader.EntryLink.Clone() 863 return 864 } 865 866 // GenChain adds the genesis entries to a newly cloned or joined chain 867 func (s *Service) GenChain(name string) (h *Holochain, err error) { 868 h, err = s.Load(name) 869 if err != nil { 870 return 871 } 872 _, err = h.GenChain() 873 if err != nil { 874 return 875 } 876 // go h.DHT().HandleChangeReqs() 877 return 878 } 879 880 // List chains produces a textual representation of the chains in the .holochain directory 881 func (s *Service) ListChains() (list string) { 882 chains, _ := s.ConfiguredChains() 883 l := len(chains) 884 if l > 0 { 885 keys := make([]string, l) 886 i := 0 887 for k := range chains { 888 keys[i] = k 889 i++ 890 } 891 sort.Strings(keys) 892 list = "installed holochains:\n" 893 for _, k := range keys { 894 id := chains[k].DNAHash() 895 var sid = "<not-started>" 896 if id.String() != "" { 897 sid = id.String() 898 } 899 list += fmt.Sprintf(" %v %v\n", k, sid) 900 bridges, _ := chains[k].GetBridges() 901 if bridges != nil { 902 for _, b := range bridges { 903 if b.Side == BridgeCaller { 904 list += fmt.Sprintf(" bridged to: %s (%v)\n", b.CalleeName, b.CalleeApp) 905 } else { 906 list += fmt.Sprintf(" bridged from by token: %v\n", b.Token) 907 } 908 } 909 } 910 } 911 912 } else { 913 list = "no installed chains" 914 } 915 return 916 } 917 918 // saveDNAFile writes out holochain DNA to files 919 func (s *Service) saveDNAFile(root string, dna *DNA, encodingFormat string, overwrite bool) (err error) { 920 dnaPath := filepath.Join(root, ChainDNADir) 921 p := filepath.Join(dnaPath, DNAFileName+"."+encodingFormat) 922 if !overwrite && FileExists(p) { 923 return mkErr(p + " already exists") 924 } 925 926 f, err := os.Create(p) 927 if err != nil { 928 return err 929 } 930 defer f.Close() 931 932 dnaFile := DNAFile{ 933 Version: dna.Version, 934 UUID: dna.UUID, 935 Name: dna.Name, 936 Properties: dna.Properties, 937 PropertiesSchemaFile: dna.PropertiesSchemaFile, 938 BasedOn: dna.BasedOn, 939 RequiresVersion: dna.RequiresVersion, 940 DHTConfig: dna.DHTConfig, 941 Progenitor: dna.Progenitor, 942 } 943 for _, z := range dna.Zomes { 944 zpath := filepath.Join(dnaPath, z.Name) 945 if err = os.MkdirAll(zpath, os.ModePerm); err != nil { 946 return 947 } 948 if err = WriteFile([]byte(z.Code), zpath, z.Name+suffixByRibosomeType(z.RibosomeType)); err != nil { 949 return 950 } 951 952 zomeFile := ZomeFile{Name: z.Name, 953 Description: z.Description, 954 CodeFile: z.CodeFileName(), 955 RibosomeType: z.RibosomeType, 956 Functions: z.Functions, 957 BridgeFuncs: z.BridgeFuncs, 958 Config: z.Config, 959 } 960 961 for _, e := range z.Entries { 962 entryDefFile := EntryDefFile{ 963 Name: e.Name, 964 DataFormat: e.DataFormat, 965 Sharing: e.Sharing, 966 } 967 if e.DataFormat == DataFormatJSON && e.Schema != "" { 968 entryDefFile.SchemaFile = e.Name + ".json" 969 if err = WriteFile([]byte(e.Schema), zpath, e.Name+".json"); err != nil { 970 return 971 } 972 } 973 974 zomeFile.Entries = append(zomeFile.Entries, entryDefFile) 975 } 976 dnaFile.Zomes = append(dnaFile.Zomes, zomeFile) 977 } 978 979 if dna.PropertiesSchema != "" { 980 if err = WriteFile([]byte(dna.PropertiesSchema), dnaPath, "properties_schema.json"); err != nil { 981 return 982 } 983 } 984 985 err = Encode(f, encodingFormat, dnaFile) 986 return 987 } 988 989 // MakeAppPackage creates a package blob from a given holochain 990 func (service *Service) MakeAppPackage(h *Holochain) (data []byte, err error) { 991 appPackage := AppPackage{ 992 Version: AppPackageVersion, 993 Generator: "holochain " + VersionStr, 994 DNA: *h.nucleus.dna, 995 } 996 997 var testsmap map[string]TestSet 998 testsmap, err = LoadTestFiles(h.TestPath()) 999 if err != nil { 1000 return 1001 } 1002 appPackage.TestSets = make([]AppPackageTests, 0) 1003 for name, t := range testsmap { 1004 appPackage.TestSets = append(appPackage.TestSets, AppPackageTests{Name: name, TestSet: t}) 1005 } 1006 1007 var scenarioFiles map[string]*os.FileInfo 1008 scenarioFiles, err = GetTestScenarios(h) 1009 if err != nil { 1010 return 1011 } 1012 appPackage.Scenarios = make([]AppPackageScenario, 0) 1013 for name, _ := range scenarioFiles { 1014 scenarioPath := filepath.Join(h.TestPath(), name) 1015 var rolemap map[string]TestSet 1016 rolemap, err = LoadTestFiles(scenarioPath) 1017 if err != nil { 1018 return 1019 } 1020 roles := make([]AppPackageTests, 0) 1021 for name, tests := range rolemap { 1022 roles = append(roles, 1023 AppPackageTests{Name: name, TestSet: tests}) 1024 1025 } 1026 scenario := AppPackageScenario{Name: name, Roles: roles} 1027 if FileExists(scenarioPath, TestConfigFileName) { 1028 var config *TestConfig 1029 config, err = LoadTestConfig(scenarioPath) 1030 if err != nil { 1031 return 1032 } 1033 scenario.Config = *config 1034 } 1035 appPackage.Scenarios = append(appPackage.Scenarios, scenario) 1036 } 1037 1038 var files []os.FileInfo 1039 files, err = ioutil.ReadDir(h.UIPath()) 1040 if err != nil { 1041 return 1042 } 1043 1044 appPackage.UI = make([]AppPackageUIFile, 0) 1045 for _, f := range files { 1046 // TODO handle subdirectories 1047 if f.Mode().IsRegular() { 1048 var file []byte 1049 file, err = ReadFile(h.UIPath(), f.Name()) 1050 if err != nil { 1051 return 1052 } 1053 uiFile := AppPackageUIFile{FileName: f.Name()} 1054 contentType := http.DetectContentType(file) 1055 if encodeAsBinary(contentType) { 1056 uiFile.Data = base64.StdEncoding.EncodeToString([]byte(file)) 1057 1058 uiFile.Encoding = "base64" 1059 } else { 1060 uiFile.Data = string(file) 1061 } 1062 appPackage.UI = append(appPackage.UI, uiFile) 1063 1064 } 1065 } 1066 1067 data, err = json.MarshalIndent(appPackage, "", " ") 1068 return 1069 } 1070 1071 func encodeAsBinary(contentType string) bool { 1072 if strings.HasPrefix(contentType, "text") { 1073 return false 1074 } 1075 return true 1076 } 1077 1078 // SaveFromAppPackage writes out a holochain application based on appPackage file to path 1079 func (service *Service) SaveFromAppPackage(reader io.Reader, path string, name string, agent Agent, decodingFormat string, encodingFormat string, newUUID bool) (appPackage *AppPackage, err error) { 1080 appPackage, err = LoadAppPackage(reader, decodingFormat) 1081 if err != nil { 1082 return 1083 } 1084 err = service.saveFromAppPackage(appPackage, path, name, encodingFormat, newUUID) 1085 if err != nil { 1086 return 1087 } 1088 if agent == nil { 1089 agent = service.DefaultAgent 1090 } 1091 err = SaveAgent(path, agent) 1092 if err != nil { 1093 return 1094 } 1095 return 1096 } 1097 1098 func (service *Service) saveFromAppPackage(appPackage *AppPackage, path string, name string, encodingFormat string, newUUID bool) (err error) { 1099 1100 dna := &appPackage.DNA 1101 err = MakeDirs(path) 1102 if err != nil { 1103 return 1104 } 1105 if newUUID { 1106 dna.NewUUID() 1107 } 1108 dna.Name = name 1109 1110 err = service.saveDNAFile(path, dna, encodingFormat, false) 1111 if err != nil { 1112 return 1113 } 1114 1115 testPath := filepath.Join(path, ChainTestDir) 1116 for _, test := range appPackage.TestSets { 1117 p := filepath.Join(testPath, test.Name+".json") 1118 var f *os.File 1119 f, err = os.Create(p) 1120 if err != nil { 1121 return 1122 } 1123 defer f.Close() 1124 err = Encode(f, "json", test.TestSet) 1125 if err != nil { 1126 return 1127 } 1128 } 1129 1130 for _, scenario := range appPackage.Scenarios { 1131 scenarioPath := filepath.Join(testPath, scenario.Name) 1132 err = os.MkdirAll(scenarioPath, os.ModePerm) 1133 if err != nil { 1134 return 1135 } 1136 for _, role := range scenario.Roles { 1137 p := filepath.Join(scenarioPath, role.Name+".json") 1138 var f *os.File 1139 f, err = os.Create(p) 1140 if err != nil { 1141 return 1142 } 1143 defer f.Close() 1144 err = Encode(f, "json", &role.TestSet) 1145 if err != nil { 1146 return 1147 } 1148 } 1149 if scenario.Config.Duration != 0 { 1150 p := filepath.Join(scenarioPath, TestConfigFileName) 1151 var f *os.File 1152 f, err = os.Create(p) 1153 if err != nil { 1154 return 1155 } 1156 defer f.Close() 1157 err = Encode(f, "json", &scenario.Config) 1158 } 1159 } 1160 1161 p := filepath.Join(path, ChainUIDir) 1162 for _, ui := range appPackage.UI { 1163 var data []byte 1164 if ui.Encoding == "base64" { 1165 data, err = base64.StdEncoding.DecodeString(ui.Data) 1166 if err != nil { 1167 return 1168 } 1169 } else { 1170 data = []byte(ui.Data) 1171 } 1172 1173 if err = WriteFile(data, p, ui.FileName); err != nil { 1174 return 1175 } 1176 } 1177 1178 return 1179 } 1180 1181 //MakeDirs creates the directory structure of an application 1182 func MakeDirs(devPath string) error { 1183 err := os.MkdirAll(devPath, os.ModePerm) 1184 if err != nil { 1185 return err 1186 } 1187 err = os.MkdirAll(filepath.Join(devPath, ChainDNADir), os.ModePerm) 1188 if err != nil { 1189 return err 1190 } 1191 err = os.MkdirAll(filepath.Join(devPath, ChainUIDir), os.ModePerm) 1192 if err != nil { 1193 return err 1194 } 1195 err = os.MkdirAll(filepath.Join(devPath, ChainTestDir), os.ModePerm) 1196 if err != nil { 1197 return err 1198 } 1199 1200 return nil 1201 } 1202 1203 // LoadTestFile unmarshals test json data 1204 func LoadTestFile(dir string, file string) (tests TestSet, err error) { 1205 var v []byte 1206 v, err = ReadFile(dir, file) 1207 if err != nil { 1208 return 1209 } 1210 err = json.Unmarshal(v, &tests) 1211 1212 if err != nil { 1213 return 1214 } 1215 return 1216 } 1217 1218 // LoadTestConfig unmarshals test json data 1219 func LoadTestConfig(dir string) (config *TestConfig, err error) { 1220 c := TestConfig{GossipInterval: 2000, Duration: 0} 1221 config = &c 1222 // if no config file return default values 1223 if !FileExists(dir, TestConfigFileName) { 1224 return 1225 } 1226 var v []byte 1227 v, err = ReadFile(dir, TestConfigFileName) 1228 if err != nil { 1229 return nil, err 1230 } 1231 err = json.Unmarshal(v, &c) 1232 1233 if err != nil { 1234 return nil, err 1235 } 1236 return 1237 } 1238 1239 // LoadTestFiles searches a path for .json test files and loads them into an array 1240 func LoadTestFiles(path string) (map[string]TestSet, error) { 1241 files, err := ioutil.ReadDir(path) 1242 if err != nil { 1243 return nil, err 1244 } 1245 1246 re := regexp.MustCompile(`(.*)\.json`) 1247 var tests = make(map[string]TestSet) 1248 for _, f := range files { 1249 if f.Mode().IsRegular() { 1250 x := re.FindStringSubmatch(f.Name()) 1251 if len(x) > 0 { 1252 if f.Name() != TestConfigFileName { 1253 name := x[1] 1254 tests[name], err = LoadTestFile(path, x[0]) 1255 if err != nil { 1256 return nil, err 1257 } 1258 } 1259 } 1260 } 1261 } 1262 1263 if len(tests) == 0 { 1264 return nil, errors.New("no test files found in: " + path) 1265 } 1266 1267 return tests, err 1268 } 1269 1270 // TestScenarioList returns a list of paths to scenario directories 1271 func GetTestScenarios(h *Holochain) (scenarios map[string]*os.FileInfo, err error) { 1272 dirContentList := []os.FileInfo{} 1273 scenarios = make(map[string]*os.FileInfo) 1274 1275 dirContentList, err = ioutil.ReadDir(h.TestPath()) 1276 if err != nil { 1277 return scenarios, err 1278 } 1279 for _, fileOrDir := range dirContentList { 1280 if fileOrDir.Mode().IsDir() { 1281 scenarios[fileOrDir.Name()] = &fileOrDir 1282 } 1283 } 1284 1285 return scenarios, err 1286 } 1287 1288 // GetTestScenarioRoles returns a list of scenario roles 1289 func GetTestScenarioRoles(h *Holochain, scenarioName string) (roleNameList []string, err error) { 1290 return GetAllTestRoles(filepath.Join(h.TestPath(), scenarioName)) 1291 } 1292 1293 // GetAllTestRoles retuns a list of the roles in a scenario 1294 func GetAllTestRoles(path string) (roleNameList []string, err error) { 1295 roleNameList = []string{} 1296 1297 files, err := ioutil.ReadDir(path) 1298 if err != nil { 1299 return nil, err 1300 } 1301 1302 // ignore all files that start with an underscore, these are either the config file or 1303 // bridge specs 1304 re := regexp.MustCompile(`^([^_].*)\.json`) 1305 for _, f := range files { 1306 if f.Mode().IsRegular() { 1307 x := re.FindStringSubmatch(f.Name()) 1308 if len(x) > 0 { 1309 roleNameList = append(roleNameList, x[1]) 1310 } 1311 } 1312 } 1313 return 1314 } 1315 1316 const ( 1317 TestingAppDecodingFormat = "json" 1318 ) 1319 1320 func TestingAppAppPackage() string { 1321 return `{ 1322 "Version": "` + AppPackageVersion + `", 1323 "Generator": "holochain service.go", 1324 "DNA": { 1325 "Version": 1, 1326 "UUID": "00000000-0000-0000-0000-000000000000", 1327 "Name": "testingApp", 1328 "RequiresVersion": ` + VersionStr + `, 1329 "Properties": { 1330 "description": "a bogus test holochain", 1331 "language": "en" 1332 }, 1333 "PropertiesSchemaFile": "properties_schema.json", 1334 "DHTConfig": { 1335 "HashType": "sha2-256" 1336 }, 1337 "Progenitor": { 1338 "Identity": "Progenitor Agent <progenitore@example.com>", 1339 "PubKey": [8, 1, 18, 32, 193, 43, 31, 148, 23, 249, 163, 154, 128, 25, 237, 167, 253, 63, 214, 220, 206, 131, 217, 74, 168, 30, 215, 237, 231, 160, 69, 89, 48, 17, 104, 210] 1340 }, 1341 "Zomes": [ 1342 { 1343 "Name": "zySampleZome", 1344 "Description": "this is a zygomas test zome", 1345 "RibosomeType": "zygo", 1346 "BridgeFuncs" :["testStrFn1"], 1347 "Entries": [ 1348 { 1349 "Name": "evenNumbers", 1350 "DataFormat": "zygo", 1351 "Sharing": "public" 1352 }, 1353 { 1354 "Name": "primes", 1355 "DataFormat": "json", 1356 "Schema": "` + jsSanitizeString(primesSchema) + `", 1357 "Sharing": "public" 1358 }, 1359 { 1360 "Name": "profile", 1361 "DataFormat": "json", 1362 "Schema": "` + jsSanitizeString(profileSchema) + `", 1363 "Sharing": "public" 1364 }, 1365 { 1366 "Name": "privateData", 1367 "DataFormat": "string", 1368 "Sharing": "private" 1369 } 1370 ], 1371 "Functions": [ 1372 { 1373 "Name": "getDNA", 1374 "CallingType": "string", 1375 "Exposure": "" 1376 }, 1377 { 1378 "Name": "addEven", 1379 "CallingType": "string", 1380 "Exposure": "public" 1381 }, 1382 { 1383 "Name": "addPrime", 1384 "CallingType": "json", 1385 "Exposure": "public" 1386 }, 1387 { 1388 "Name": "confirmOdd", 1389 "CallingType": "string", 1390 "Exposure": "public" 1391 }, 1392 { 1393 "Name": "testStrFn1", 1394 "CallingType": "string", 1395 "Exposure": "" 1396 }, 1397 { 1398 "Name": "testStrFn2", 1399 "CallingType": "string", 1400 "Exposure": "" 1401 }, 1402 { 1403 "Name": "testJsonFn1", 1404 "CallingType": "json", 1405 "Exposure": "" 1406 }, 1407 { 1408 "Name": "testJsonFn2", 1409 "CallingType": "json", 1410 "Exposure": "" 1411 }, 1412 { 1413 "Name": "myIdentity", 1414 "CallingType": "string" 1415 } 1416 ], 1417 "Code": "` + jsSanitizeString(zygoZomeCode) + `" 1418 }, 1419 { 1420 "Name": "jsSampleZome", 1421 "Description": "this is a javascript test zome", 1422 "RibosomeType": "js", 1423 "BridgeFuncs" :["getProperty"], 1424 "Entries": [ 1425 { 1426 "Name": "oddNumbers", 1427 "DataFormat": "js", 1428 "Sharing": "public" 1429 }, 1430 { 1431 "Name": "profile", 1432 "DataFormat": "json", 1433 "Schema": "` + jsSanitizeString(profileSchema) + `", 1434 "Sharing": "public" 1435 }, 1436 { 1437 "Name": "rating", 1438 "DataFormat": "links" 1439 }, 1440 { 1441 "Name": "review", 1442 "DataFormat": "string", 1443 "Sharing": "public" 1444 }, 1445 { 1446 "Name": "secret", 1447 "DataFormat": "string" 1448 } 1449 ], 1450 "Functions": [ 1451 { 1452 "Name": "getProperty", 1453 "CallingType": "string", 1454 "Exposure": "public" 1455 }, 1456 { 1457 "Name": "addOdd", 1458 "CallingType": "string", 1459 "Exposure": "public" 1460 }, 1461 { 1462 "Name": "addProfile", 1463 "CallingType": "json", 1464 "Exposure": "public" 1465 }, 1466 { 1467 "Name": "testStrFn1", 1468 "CallingType": "string", 1469 "Exposure": "" 1470 }, 1471 { 1472 "Name": "testStrFn2", 1473 "CallingType": "string", 1474 "Exposure": "" 1475 }, 1476 { 1477 "Name": "testJsonFn1", 1478 "CallingType": "json", 1479 "Exposure": "" 1480 }, 1481 { 1482 "Name": "testJsonFn2", 1483 "CallingType": "json", 1484 "Exposure": "" 1485 }, 1486 { 1487 "Name": "throwError", 1488 "CallingType": "string", 1489 "Exposure": "public" 1490 } 1491 ], 1492 "Code": "` + jsSanitizeString(jsZomeCode) + `" 1493 } 1494 ]}, 1495 "TestSets":[{"Name":"testSet1","TestSet":{ 1496 "Identity": "123-456-7890", 1497 "Tests":[ 1498 { 1499 "Zome": "zySampleZome", 1500 "FnName": "addEven", 1501 "Input": "2", 1502 "Output": "%h%" 1503 }, 1504 { 1505 "Zome": "zySampleZome", 1506 "FnName": "addEven", 1507 "Input": "4", 1508 "Output": "%h%" 1509 }, 1510 { 1511 "Zome": "zySampleZome", 1512 "FnName": "addEven", 1513 "Input": "5", 1514 "Err": "Error calling 'commit': Validation Failed: 5 is not even" 1515 }, 1516 { 1517 "Zome": "zySampleZome", 1518 "FnName": "addPrime", 1519 "Input": {"prime":7}, 1520 "Output": "%h%" 1521 }, 1522 { 1523 "Zome": "zySampleZome", 1524 "FnName": "addPrime", 1525 "Input": {"prime":4}, 1526 "Err": "Error calling 'commit': Validation Failed" 1527 }, 1528 { 1529 "Zome": "jsSampleZome", 1530 "FnName": "addProfile", 1531 "Input": {"firstName":"Art","lastName":"Brock"}, 1532 "Output": "%h%" 1533 }, 1534 { 1535 "Zome": "zySampleZome", 1536 "FnName": "getDNA", 1537 "Input": "", 1538 "Output": "%dna%" 1539 }, 1540 { 1541 "Zome": "zySampleZome", 1542 "FnName": "getDNA", 1543 "Input": "", 1544 "Err": "function not available", 1545 "Exposure": "public" 1546 }, 1547 { 1548 "Zome": "zySampleZome", 1549 "FnName": "myIdentity", 1550 "Input": "", 1551 "Output": "123-456-7890" 1552 } 1553 ]}}, 1554 {"Name":"testSet2","TestSet":{ 1555 "Fixtures":{ 1556 "Agents":[{"Hash":"QmVGtdTZdTFaLsaj2RwdVG8jcjNNcp1DE914DKZ2kHmXHx","Identity":"agent@foo.com"}] 1557 }, 1558 "Tests":[ 1559 { 1560 "Zome": "jsSampleZome", 1561 "FnName": "addOdd", 1562 "Input": "7", 1563 "Output": "%h%" 1564 }, 1565 { 1566 "Zome": "jsSampleZome", 1567 "FnName": "addOdd", 1568 "Input": "2", 1569 "Err": {"errorMessage":"Validation Failed: 2 is not odd","function":"commit","name":"` + HolochainErrorPrefix + `","source":{"column":"28","functionName":"addOdd","line":"45"}} 1570 }, 1571 { 1572 "Zome": "jsSampleZome", 1573 "FnName": "addOdd", 1574 "Input": "2", 1575 "ErrMsg": "Validation Failed: 2 is not odd" 1576 }, 1577 { 1578 "Zome": "zySampleZome", 1579 "FnName": "confirmOdd", 1580 "Input": "9", 1581 "Output": "false" 1582 }, 1583 { 1584 "Zome": "zySampleZome", 1585 "FnName": "confirmOdd", 1586 "Input": "7", 1587 "Output": "true" 1588 }, 1589 { 1590 "Zome": "jsSampleZome", 1591 "Input": "unexposed(\"this is a\")", 1592 "Output": "this is a fish", 1593 "Raw": true 1594 }, 1595 { 1596 "Convey": "test the output of a function that returns json", 1597 "Zome": "jsSampleZome", 1598 "FnName": "testJsonFn2", 1599 "Input": "", 1600 "Output": [{"a":"b"}] 1601 }, 1602 { 1603 "Convey": "agent fixture substitution works", 1604 "Zome": "jsSampleZome", 1605 "Input": "\"%agent0%--%agent0_str%\"", 1606 "Output": "QmVGtdTZdTFaLsaj2RwdVG8jcjNNcp1DE914DKZ2kHmXHx--agent@foo.com", 1607 "Raw": true 1608 } 1609 ]}}], 1610 "UI":[ 1611 {"FileName":"index.html", 1612 "Data":"` + jsSanitizeString(SampleHTML) + `" 1613 }, 1614 {"FileName":"hc.js", 1615 "Data":"` + jsSanitizeString(SampleJS) + `" 1616 }, 1617 {"FileName":"logo.png", 1618 "Data":"` + jsSanitizeString(SampleBinary) + `", 1619 "Encoding":"base64" 1620 }], 1621 "Scenarios":[ 1622 {"Name":"sampleScenario", 1623 "Roles":[ 1624 {"Name":"speaker", 1625 "TestSet":{"Tests":[ 1626 {"Convey":"add an odd", 1627 "Zome": "jsSampleZome", 1628 "FnName": "addOdd", 1629 "Input": "7", 1630 "Output": "%h%" 1631 }, 1632 {"Convey":"raw function with scenario substitutions that fails", 1633 "Zome": "jsSampleZome", 1634 "Raw": true, 1635 "Input": "'foo'", 1636 "Output": "%server% %listener_hash% %listener_str%" 1637 } 1638 ]}}, 1639 {"Name":"listener", 1640 "TestSet":{"Tests":[ 1641 {"Convey":"confirm prime exists", 1642 "Zome": "zySampleZome", 1643 "FnName": "confirmOdd", 1644 "Input": "7", 1645 "Output": "true", 1646 "Time" : 1500 1647 } 1648 ]}} 1649 ], 1650 "Config":{"Duration":5,"GossipInterval":300}}] 1651 } 1652 ` 1653 } 1654 1655 const ( 1656 profileSchema = `{ 1657 "title": "Profile Schema", 1658 "type": "object", 1659 "properties": { 1660 "firstName": { 1661 "type": "string" 1662 }, 1663 "lastName": { 1664 "type": "string" 1665 }, 1666 "age": { 1667 "description": "Age in years", 1668 "type": "integer", 1669 "minimum": 0 1670 } 1671 }, 1672 "required": ["firstName", "lastName"] 1673 }` 1674 1675 primesSchema = ` 1676 { 1677 "title": "Prime Schema", 1678 "type": "object", 1679 "properties": { 1680 "prime": { 1681 "type": "integer" 1682 } 1683 }, 1684 "required": ["prime"] 1685 }` 1686 1687 jsZomeCode = ` 1688 function unexposed(x) {return x+" fish";}; 1689 function testStrFn1(x) {return "result: "+x}; 1690 function testStrFn2(x){ return parseInt(x)+2}; 1691 function testJsonFn1(x){ x.output = x.input*2; return x;}; 1692 function testJsonFn2(x){ return [{a:'b'}] }; 1693 1694 function getProperty(x) {return property(x)}; 1695 function addOdd(x) {return commit("oddNumbers",x);} 1696 function addProfile(x) {return commit("profile",x);} 1697 function throwError(x) {throw new Error(x)} 1698 function validatePut(entry_type,entry,header,pkg,sources) { 1699 return validate(entry_type,entry,header,sources); 1700 } 1701 function validateMod(entry_type,entry,header,replaces,pkg,sources) { 1702 return true; 1703 } 1704 function validateDel(entry_type,hash,pkg,sources) { 1705 return true; 1706 } 1707 function validateCommit(entry_type,entry,header,pkg,sources) { 1708 if (entry_type == "rating") {return true} 1709 return validate(entry_type,entry,header,sources); 1710 } 1711 function validate(entry_type,entry,header,sources) { 1712 if (entry_type=="oddNumbers") { 1713 return (entry%2 != 0) ? true : entry+" is not odd" 1714 } 1715 if (entry_type=="profile") { 1716 return true 1717 } 1718 if (entry_type=="review") { 1719 return true 1720 } 1721 if (entry_type=="secret") { 1722 return true 1723 } 1724 if (entry_type=="rating") { 1725 return true 1726 } 1727 return false 1728 } 1729 function validateLink(linkEntryType,baseHash,linkHash,tag,pkg,sources){return true} 1730 function validatePutPkg(entry_type) { 1731 req = {}; 1732 req[HC.PkgReq.Chain]=HC.PkgReq.ChainOpt.Full; 1733 return req; 1734 } 1735 function validateModPkg(entry_type) { return null} 1736 function validateDelPkg(entry_type) { return null} 1737 function validateLinkPkg(entry_type) { return null} 1738 1739 function genesis() { 1740 debug("running jsZome genesis") 1741 return true 1742 } 1743 function bridgeGenesis(side,app,data) { 1744 testGetBridges(); 1745 return true 1746 } 1747 1748 function bundleCanceled(reason,userParam) { 1749 debug(userParam+"debug message during bundleCanceled with reason: "+reason); 1750 if (userParam == 'debugit') { 1751 debug("debug message during bundleCanceled with reason: "+reason); 1752 } else if (userParam == 'cancelit') { 1753 debug("debug message during bundleCanceled: canceling cancel!"); 1754 return HC.BundleCancel.Response.Commit 1755 } 1756 return HC.BundleCancel.Response.OK 1757 } 1758 1759 function receive(from,message) { 1760 // if the message requests blocking run an infinite loop 1761 // this is used by the async send test to force the condition where 1762 // the receiver doesn't return soon enough so that the send will timeout 1763 if (message.block) { 1764 while(true){}; 1765 } 1766 1767 // send back a pong message of what came in the ping message! 1768 return {pong:message.ping} 1769 } 1770 1771 function testGetBridges() { 1772 debug("testGetBridges:"+JSON.stringify(getBridges())) 1773 } 1774 1775 function asyncPing(message,id) { 1776 debug("async result of message with "+id+" was: "+JSON.stringify(message)) 1777 } 1778 ` 1779 zygoZomeCode = ` 1780 (defn testStrFn1 [x] (concat "result: " x)) 1781 (defn testStrFn2 [x] (+ (atoi x) 2)) 1782 (defn testJsonFn1 [x] (begin (hset x output: (* (-> x input:) 2)) x)) 1783 (defn testJsonFn2 [x] (unjson (raw "[{\"a\":\"b\"}]"))) (defn getDNA [x] App_DNA_Hash) 1784 (defn addEven [x] (commit "evenNumbers" x)) 1785 (defn addPrime [x] (commit "primes" x)) 1786 (defn confirmOdd [x] 1787 (letseq [h (makeHash "oddNumbers" x) 1788 r (get h) 1789 err (cond (hash? r) (hget r %error "") "found")] 1790 (cond (== err "") "true" "false") 1791 ) 1792 ) 1793 (defn validateCommit [entryType entry header pkg sources] 1794 (validate entryType entry header sources)) 1795 (defn validatePut [entryType entry header pkg sources] 1796 (validate entryType entry header sources)) 1797 (defn validateMod [entryType entry header replaces pkg sources] true) 1798 (defn validateDel [entryType hash pkg sources] true) 1799 (defn validate [entryType entry header sources] 1800 (cond (== entryType "evenNumbers") (cond (== (mod entry 2) 0) true (concat (str entry) " is not even")) 1801 (== entryType "primes") (isprime (hget entry %prime)) 1802 (== entryType "profile") "" 1803 false) 1804 ) 1805 (defn validateLink [linkEntryType baseHash links pkg sources] true) 1806 (defn validatePutPkg [entryType] nil) 1807 (defn validateModPkg [entryType] nil) 1808 (defn validateDelPkg [entryType] nil) 1809 (defn validateLinkPkg [entryType] nil) 1810 (defn genesis [] 1811 (debug "running zyZome genesis") 1812 true 1813 ) 1814 (defn bridgeGenesis [side app data] (begin (debug (concat "bridge genesis " (cond (== side HC_Bridge_Caller) "from" "to") "-- other side is:" app " bridging data:" data)) true)) 1815 (defn receive [from message] 1816 (hash pong: (hget message %ping))) 1817 1818 (defn testGetBridges [] 1819 (debug (str (getBridges)))) 1820 1821 (defn asyncPing [message,id] 1822 (debug (concat "async result of message with " id " was:" (str message))) 1823 ) 1824 1825 (defn myIdentity [x] App_Agent_String) 1826 ` 1827 1828 SampleHTML = ` 1829 <html> 1830 <head> 1831 <title>Test</title> 1832 <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script> 1833 <script type="text/javascript" src="/hc.js"> 1834 </script> 1835 </head> 1836 <body> 1837 <img src="logo.png"> 1838 <select id="zome" name="zome"> 1839 <option value="zySampleZome">zySampleZome</option> 1840 <option value="jsSampleZome">jsSampleZome</option> 1841 </select> 1842 <select id="fn" name="fn"> 1843 <option value="addEven">addEven</option> 1844 <option value="getProperty">getProperty</option> 1845 <option value="addPrime">addPrime</option> 1846 </select> 1847 <input id="data" name="data"> 1848 <button onclick="send();">Send</button> 1849 send an even number and get back a hash, send and odd end get a error 1850 1851 <div id="result"></div> 1852 <div id="err"></div> 1853 </body> 1854 </html>` 1855 SampleJS = ` 1856 function send() { 1857 $.post( 1858 "/fn/"+$('select[name=zome]').val()+"/"+$('select[name=fn]').val(), 1859 $('#data').val(), 1860 function(data) { 1861 $("#result").html("result:"+data) 1862 $("#err").html("") 1863 } 1864 ).error(function(response) { 1865 $("#err").html(response.responseText) 1866 $("#result").html("") 1867 }) 1868 ; 1869 }; 1870 ` 1871 SampleBinary = `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 1919 AABJRU5ErkJggg== 1920 ` 1921 )