From 1b7bd7f28f2a77bc0b57dae15189143b81dd8ab3 Mon Sep 17 00:00:00 2001 From: Alexandre Bourget Date: Thu, 3 May 2018 13:00:28 -0400 Subject: [PATCH] Bitcoin -> Ethereum clock. Hook updates: boot_connect_mesh, boot_network -> boot_node publish_kickstart_data -> boot_publish_genesis, join_network.. --- README-cn.md | 14 -- README.md | 72 +++++--- bios.go | 164 ++++++++++-------- eos-bios/cmd/boot.go | 4 +- eos-bios/cmd/discover.go | 2 +- eos-bios/cmd/join.go | 2 +- eos-bios/cmd/orchestrate.go | 2 +- eos-bios/cmd/root.go | 2 +- bitcoinclock.go => ethclock.go | 39 ++++- bitcoinclock_test.go => ethclock_test.go | 11 ++ genesis.go | 38 ++++ hooks.go | 38 +++- ipfs.go | 20 +-- launchdata.go | 2 +- network.go | 8 +- sample_config/README.md | 2 +- sample_config/hook_boot_connect_mesh.sh | 22 +++ ...hook_boot_network.sh => hook_boot_node.sh} | 5 +- sample_config/hook_boot_publish_genesis.sh | 20 +++ sample_config/hook_boot_publish_handoff.sh | 18 ++ sample_config/hook_join_network.sh | 2 +- sample_config/hook_publish_kickstart_data.sh | 6 - sample_config/my_discovery_file.yaml | 8 +- utils.go | 5 + 24 files changed, 343 insertions(+), 163 deletions(-) rename bitcoinclock.go => ethclock.go (62%) rename bitcoinclock_test.go => ethclock_test.go (69%) create mode 100644 sample_config/hook_boot_connect_mesh.sh rename sample_config/{hook_boot_network.sh => hook_boot_node.sh} (92%) create mode 100644 sample_config/hook_boot_publish_genesis.sh create mode 100755 sample_config/hook_boot_publish_handoff.sh delete mode 100755 sample_config/hook_publish_kickstart_data.sh diff --git a/README-cn.md b/README-cn.md index ecf0899..16be8de 100644 --- a/README-cn.md +++ b/README-cn.md @@ -112,17 +112,3 @@ To join a network, tweak your discovery file to point to the network you're tryi 加入我们的 telegram 讨论组: https://t.me/joinchat/GSUv1UaI5QIuifHZs8k_eA (EOSIO BIOS Boot channel) - - - - -TODO ----- - -* Shuffling of the top 5 for Boot selection -* Wait on Bitcoin Block - * Add bitcoin_block_height in LaunchData -* In Orchestrate, compute the LaunchData by the most votes, weighted by the highest Weight -* Do we auto-publish the `my_discovery_file.yaml` ? Make it a hook? -* canonical_url ? -* convention regarding URLs, which pieces we want to see in there (the name of the organization, `testnet-[network_name]) diff --git a/README.md b/README.md index d85e2e3..c6c5263 100644 --- a/README.md +++ b/README.md @@ -89,18 +89,51 @@ When the time comes to orchestrate a launch, *everyone* will run: eos-bios orchestrate According to an algorithm, and using the network discovery data, each -team will be assigned a role deterministically. +team will be assigned a role deterministically: -You then fall in one of these three categories: - -1. The BIOS Boot node, which will, alone, execute the equivalent of `eos-bios boot`. -2. An Appointed Block Producer, which executes the equivalent of `eos-bios join --verify` -3. An other participant, which executes the equivalent of `eos-bios join` +1. The _BIOS Boot node_, which will, alone, execute the equivalent of `eos-bios boot`. +2. An _Appointed Block Producer_, which executes the equivalent of `eos-bios join --verify` +3. An _other participant_, which executes the equivalent of `eos-bios join` The same hooks are used in `boot`, `join` and `orchestrate`, so get them right and practice. +Example flow and interventions in the orchestrated launch +--------------------------------------------------------- + +1. Everyone runs `eos-bios orchestrate`. +1. `eos-bios` downloads the network topology pointed to by your `my_discovery_file.yaml`, as does everyone. +1. The network topology is sorted by weight according to how people voted in their `peers` section. +1. The `launch_ethereum_block` is taken from the top 20% in the topology: if they all agree, with continue with that number. Otherwise, we wait until they do (and periodically retraverse the network graph) + + + +Network Discovery Protocol +-------------------------- + +The Network Discovery Protocol starts with a simple file (see +`my_discovery_file.yaml` in the `sample_config` dir), which is +published to IPFS, and linked through IPNS (like a DNS on IPFS). This +provides a reference that points to your `my_discovery_file.yaml` from +anyone connected to the IPFS network. It looks like: +`/ipns/QmYRsQNxAZFvx8djAxKsgurJT1RF47MhAEQ2sLz1MunnXH`. The last part +is a hash of the public key of the `ipfs` instance running on +someone's computer (which holds the corresponding private key). + +In the `my_discovery_file.yaml`, there is (under `launch_data`) a +`peers` key. This allow you to point to other block producer's +published `/ipns/Qm...` link. You can also `weight` the link. + +By traversing these links, we can build an in-memory graph of all the +peers connected to one another. Everyone is free to publish when they +want. No need for centralized spreadsheet. + +The graph that is created this way can be sorted according to who was +voted for the most. It is public (so don't try to screw anyone), and a +public commitment of whom you're willing to launch with. It is a +decentralized way to vouch for other Block Producers. + Install / Download ------------------ @@ -135,28 +168,11 @@ See the previous proposition in this repo in README.v0.md TODO ---- -* Shuffling of the top 5 for Boot selection -* Upon `orchestrate`, wait on Bitcoin Block - * TODO: Add bitcoin_block_height in LaunchData - * Sync on most popular Bitcoin block. - * Mark in discovery file that "eosio_p2p_address", make it REQUIRED. - * WAIT for the BTC block, then REDISCOVER the network (everyone together, to get a final version, sync'd). - * Then GO orchestrate. - * In Orchestrate, compute the LaunchData by the most votes, weighted by the highest Weight -* Do we auto-publish the `my_discovery_file.yaml` ? Make it a hook? -* canonical_url ? -* convention regarding URLs, which pieces we want to see in there (the name of the organization, `testnet-[network_name]) - -* Allow ABPs to publish their `secret-p2p-address` (called Meshing - data ?) while the BIOS Boot node is doing its job. - * The pasted data would simply be added to the list of addresses - passed to the `join_network` hook when the BIOS Boot is ready - and we receive the Kickstart data. - * Both be a base64 encoded JSON: - * `type=genesis` with the genesis JSON, which includes the public key, perhaps the pubkey on the side.. - * `type=meshing` with the p2p IP/host to connect to - * `type=handoff` with the private key used to do everything. + +* Implement more ethereum sources, so we're not blocked by DDoS of those websites. + * Could we RPC directly to a swarm of nodes ? + * We could also load some from the disk, like `ethereum_swarm.txt` * output.log -> output EVERYTHING to a file, hook a Tee on `os.Stderr` and `os.Stdout`. @@ -164,8 +180,8 @@ TODO * No publishing of `secret-p2p-address`, only a remote control of `/v1/net/connect` to some ABPs who have published and established the network. - * hook_boot_network.sh * hook_boot_publish_genesis.sh + * hook_boot_node.sh * hook_boot_connect_mesh.sh * hook_boot_publish_privkey.sh diff --git a/bios.go b/bios.go index f88c60f..62abcd3 100644 --- a/bios.go +++ b/bios.go @@ -1,7 +1,6 @@ package bios import ( - "encoding/base64" "encoding/hex" "encoding/json" "fmt" @@ -24,7 +23,7 @@ type BIOS struct { Snapshot Snapshot BootSequence []*OperationType - KickstartData *KickstartData + Genesis *GenesisJSON // ShuffledProducers is an ordered list of producers according to // the shuffled peers. @@ -47,8 +46,8 @@ func NewBIOS(network *Network, api *eos.API) *BIOS { return b } -func (b *BIOS) SetKickstartData(ks *KickstartData) { - b.KickstartData = ks +func (b *BIOS) SetGenesis(gen *GenesisJSON) { + b.Genesis = gen } func (b *BIOS) Init() error { @@ -110,12 +109,12 @@ func (b *BIOS) StartOrchestrate(secretP2PAddress string) error { fmt.Println("Showing pre-randomized network discovered:") b.Network.PrintOrderedPeers() - b.RandSource = b.waitBitcoinBlock() + b.RandSource = b.waitEthereumBlock() // Once we have it, we can discover the net again (unless it's been discovered VERY recently) // and we b.Init() again.. so load the latest version of the LaunchData according to this // potentially new discovery network. - fmt.Println("Bitcoin block used to seed randomization, updating graph one last time...") + fmt.Println("Ethereum block used to seed randomization, updating graph one last time...") if err := b.Network.UpdateGraph(); err != nil { return fmt.Errorf("orchestrate: update graph: %s", err) @@ -125,7 +124,7 @@ func (b *BIOS) StartOrchestrate(secretP2PAddress string) error { b.Network.PrintOrderedPeers() if err := b.DispatchInit("orchestrate"); err != nil { - return fmt.Errorf("failed init hook: %s", err) + return fmt.Errorf("dispatch init hook: %s", err) } switch b.MyRole() { @@ -152,7 +151,7 @@ func (b *BIOS) StartJoin(verify bool) error { b.Network.PrintOrderedPeers() if err := b.DispatchInit("join"); err != nil { - return fmt.Errorf("failed init hook: %s", err) + return fmt.Errorf("dispatch init hook: %s", err) } if err := b.RunJoinNetwork(verify, false); err != nil { @@ -168,7 +167,7 @@ func (b *BIOS) StartBoot(secretP2PAddress string) error { b.Network.PrintOrderedPeers() if err := b.DispatchInit("boot"); err != nil { - return fmt.Errorf("failed init hook: %s", err) + return fmt.Errorf("dispatch init hook: %s", err) } if err := b.RunBootSequence(secretP2PAddress); err != nil { @@ -234,8 +233,12 @@ func (b *BIOS) RunBootSequence(secretP2PAddress string) error { genesisData := b.GenerateGenesisJSON(pubKey) - if err = b.DispatchBootNetwork(genesisData, pubKey, privKey); err != nil { - return fmt.Errorf("dispatch config_ready hook: %s", err) + if err = b.DispatchBootPublishGenesis(genesisData); err != nil { + return fmt.Errorf("dispatch boot_publish_genesis hook: %s", err) + } + + if err = b.DispatchBootNode(genesisData, pubKey, privKey); err != nil { + return fmt.Errorf("dispatch boot_node hook: %s", err) } fmt.Println(b.EOSAPI.Signer.AvailableKeys()) @@ -258,50 +261,25 @@ func (b *BIOS) RunBootSequence(secretP2PAddress string) error { } } - fmt.Println("Preparing kickstart data") - - kickstartData := &KickstartData{ - BIOSP2PAddress: secretP2PAddress, - PublicKeyUsed: pubKey, - PrivateKeyUsed: privKey, - GenesisJSON: genesisData, - } - kd, _ := json.Marshal(kickstartData) - ksdata := base64.RawStdEncoding.EncodeToString(kd) - - // TODO: encrypt it for those who need it - - fmt.Println("PUBLISH THIS KICKSTART DATA:") - fmt.Println("") - fmt.Println(ksdata) - fmt.Println("") - - if err = b.DispatchPublishKickstartData(ksdata); err != nil { - return fmt.Errorf("dispatch publish_kickstart_data: %s", err) + if err = b.DispatchBootPublishHandoff(); err != nil { + return fmt.Errorf("dispatch boot_publish_handoff: %s", err) } return nil } func (b *BIOS) RunJoinNetwork(verify, sabotage bool) error { - if b.KickstartData == nil { - kickstart, err := b.waitOnKickstartData() - if err != nil { - return err - } - b.KickstartData = &kickstart - + if b.Genesis == nil { + b.Genesis = b.waitOnGenesisData() } // Create mesh network otherPeers := b.computeMyMeshP2PAddresses() - if err := b.DispatchJoinNetwork(b.KickstartData, b.MyPeers, otherPeers); err != nil { - return err + if err := b.DispatchJoinNetwork(b.Genesis, b.MyPeers, otherPeers); err != nil { + return fmt.Errorf("dispatch join_network hook: %s", err) } - fmt.Println("Not doing any validation, the ABPs have done it") - if verify { fmt.Println("###############################################################################################") fmt.Println("Launching chain verification") @@ -342,6 +320,15 @@ func (b *BIOS) RunJoinNetwork(verify, sabotage bool) error { } fmt.Println("Chain sync'd!") + + // IMPLEMENT THE BOOT SEQUENCE VERIFICATION. + fmt.Println("") + fmt.Println("All good! Chain verificaiton succeeded!") + fmt.Println("") + } else { + fmt.Println("") + fmt.Println("Not doing validation, the Appointed Block Producer will have done it.") + fmt.Println("") } // TODO: loop operations, check all actions against blocks that you can fetch from here. @@ -350,23 +337,28 @@ func (b *BIOS) RunJoinNetwork(verify, sabotage bool) error { // - anything fails, SABOTAGE // Publish a PGP Signed message with your local IP.. push to properties // Dispatch webhook PublishKickstartPublic (with a Kickstart Data object) + fmt.Println("Awaiting for private key, for handoff verification.") + fmt.Println("* This is the last step, and is done for the BIOS Boot node to prove it kept nothing to itself.") + fmt.Println("") + + b.waitOnHandoff(b.Genesis) return nil } -func (b *BIOS) waitBitcoinBlock() rand.Source { +func (b *BIOS) waitEthereumBlock() rand.Source { for { - hash, err := PollBitcoinClock(b.LaunchData.LaunchBitcoinBlock) + hash, err := PollEthereumClock(b.LaunchData.LaunchEthereumBlock) if err != nil { - fmt.Println("couldn't fetch bitcoin block:", err) + fmt.Println("couldn't fetch ethereum block:", err) } else { if hash == "" { - fmt.Println("block", b.LaunchData.LaunchBitcoinBlock, "not produced yet..") + fmt.Println("block", b.LaunchData.LaunchEthereumBlock, "not produced yet..") } else { bytes, err := hex.DecodeString(hash) if err != nil { - fmt.Printf("bitcoin service returned invalid hex %q\n", hash) + fmt.Printf("ethereum service returned invalid hex %q\n", hash) } else { chksum := crc64.Checksum(bytes, crc64.MakeTable(crc64.ECMA)) return rand.NewSource(int64(chksum)) @@ -378,9 +370,9 @@ func (b *BIOS) waitBitcoinBlock() rand.Source { } } -func (b *BIOS) waitOnKickstartData() (kickstart KickstartData, err error) { +func (b *BIOS) waitOnGenesisData() (genesis *GenesisJSON) { fmt.Println("") - fmt.Println("The BIOS node will publish the Kickstart Data through their social media.") + fmt.Println("The BIOS node will publish the Genesis data through their social media.") bootNode := b.ShuffledProducers[0] disco := bootNode.Discovery if disco.Website != "" { @@ -416,39 +408,63 @@ func (b *BIOS) waitOnKickstartData() (kickstart KickstartData, err error) { if disco.SocialGitHub != "" { fmt.Println(" GitHub:", disco.SocialGitHub) } - // TODO: print the social media properties of the BP.. - fmt.Println("Paste it here and finish with two blank lines (ENTER twice):") fmt.Println("") + // TODO: print the social media properties of the BP.. + fmt.Println("Genesis data can be base64-encoded JSON, raw JSON or an `/ipfs/Qm...` link pointing to genesis.json") - // Wait on stdin for kickstart data (will we have some other polling / subscription mechanisms?) - // Accept any base64, unpadded, multi-line until we receive a blank line, concat and decode. - // FIXME: this is a quick hack to just pass the p2p address - lines, err := ScanLinesUntilBlank() - if err != nil { - return - } + for { + fmt.Printf("Paste genesis here: ") + text, err := ScanSingleLine() + if err != nil { + fmt.Println("error reading line:", err) + continue + } - rawKickstartData, err := base64.RawStdEncoding.DecodeString(strings.Replace(strings.TrimSpace(lines), "\n", "", -1)) - if err != nil { - return kickstart, fmt.Errorf("kickstart base64 decode: %s", err) - } + genesis, err := readGenesisData(text, b.Network.IPFS) + if err != nil { + fmt.Println(err) + } - err = json.Unmarshal(rawKickstartData, &kickstart) - if err != nil { - return kickstart, fmt.Errorf("unmarshal kickstart data: %s", err) + return genesis } +} - privKey, err := ecc.NewPrivateKey(kickstart.PrivateKeyUsed) - if err != nil { - return kickstart, fmt.Errorf("unable to load private key %q: %s", kickstart.PrivateKeyUsed, err) - } +func (b *BIOS) waitOnHandoff(genesis *GenesisJSON) { + for { + fmt.Printf("Please paste the private key (or ipfs link): ") + privKey, err := ScanSingleLine() + if err != nil { + fmt.Println("Error reading line:", err) + continue + } - b.EphemeralPrivateKey = privKey + if strings.Contains(privKey, "/ipfs") { + cnt, err := b.Network.IPFS.Get(IPFSRef(privKey)) + if err != nil { + fmt.Println("error fetching ipfs content:", err) + continue + } - // TODO: check if the privKey corresponds to the public key sent, if not, we should - // drop that kickstart data.. and listen to another one.. + privKey = string(cnt) + } - return + key, err := ecc.NewPrivateKey(privKey) + if err != nil { + fmt.Println("Invalid private key pasted:", err) + continue + } + + if key.PublicKey().String() == genesis.InitialKey { + fmt.Println("") + fmt.Println(" HANDOFF VERIFIED! EOS CHAIN IS ALIVE !") + fmt.Println("") + return + } else { + fmt.Println("") + fmt.Println(" WARNING: private key provided does NOT match the genesis data") + fmt.Println("") + } + } } func (b *BIOS) GenerateEphemeralPrivKey() (*ecc.PrivateKey, error) { @@ -458,7 +474,7 @@ func (b *BIOS) GenerateEphemeralPrivKey() (*ecc.PrivateKey, error) { func (b *BIOS) GenerateGenesisJSON(pubKey string) string { // known not to fail cnt, _ := json.Marshal(&GenesisJSON{ - InitialTimestamp: time.Now().UTC().Format("2006-01-02T15:04:05"), // TODO: becomes the bitcoin block if we use that as a seed for randomization? just the current time/date ? + InitialTimestamp: time.Now().UTC().Format("2006-01-02T15:04:05"), InitialKey: pubKey, InitialChainID: hex.EncodeToString(b.EOSAPI.ChainID), }) diff --git a/eos-bios/cmd/boot.go b/eos-bios/cmd/boot.go index 3435978..ef6fe07 100644 --- a/eos-bios/cmd/boot.go +++ b/eos-bios/cmd/boot.go @@ -29,14 +29,14 @@ import ( var bootCmd = &cobra.Command{ Use: "boot", Short: "Triggers hooks to boot a new network or node", - Long: `This will run the "boot_network" hook with data generated locally for a new network. + Long: `This will run the "boot_node" hook with data generated locally for a new network. The "publish_kickstart_data" will also be run, giving you the opportunity to disseminate what is required for people to join your network. Boot is what happens when you run "eos-bios orchestrate" and you are selected to be the BIOS Boot node. `, Run: func(cmd *cobra.Command, args []string) { - ipfs, err := bios.NewIPFS(ipfsGatewayAddress, ipfsLocalGatewayAddress) + ipfs, err := bios.NewIPFS(ipfsLocalGatewayAddress, ipfsGatewayAddress) if err != nil { fmt.Println("ipfs client error:", err) os.Exit(1) diff --git a/eos-bios/cmd/discover.go b/eos-bios/cmd/discover.go index 4846425..06fe9e6 100644 --- a/eos-bios/cmd/discover.go +++ b/eos-bios/cmd/discover.go @@ -27,7 +27,7 @@ var discoverCmd = &cobra.Command{ Short: "Discover and update info about all peers in the network, based on an initial discovery URL", Long: `This uses the "network.seed_discovery_url" key in your configuration to start discovery.`, Run: func(cmd *cobra.Command, args []string) { - ipfs, err := bios.NewIPFS(ipfsGatewayAddress, ipfsLocalGatewayAddress) + ipfs, err := bios.NewIPFS(ipfsLocalGatewayAddress, ipfsGatewayAddress) if err != nil { fmt.Println("ipfs client error:", err) os.Exit(1) diff --git a/eos-bios/cmd/join.go b/eos-bios/cmd/join.go index 661317e..4d9c1dd 100644 --- a/eos-bios/cmd/join.go +++ b/eos-bios/cmd/join.go @@ -31,7 +31,7 @@ var joinCmd = &cobra.Command{ Short: "Triggers the hooks to join an already running network", Long: `This will run the "join_network" hook with data discovered from the network pointed to by the seed_discovery_url.`, Run: func(cmd *cobra.Command, args []string) { - ipfs, err := bios.NewIPFS(ipfsGatewayAddress, ipfsLocalGatewayAddress) + ipfs, err := bios.NewIPFS(ipfsLocalGatewayAddress, ipfsGatewayAddress) if err != nil { fmt.Println("ipfs client error:", err) os.Exit(1) diff --git a/eos-bios/cmd/orchestrate.go b/eos-bios/cmd/orchestrate.go index f6b0dd6..f55841d 100644 --- a/eos-bios/cmd/orchestrate.go +++ b/eos-bios/cmd/orchestrate.go @@ -31,7 +31,7 @@ var orchestrateCmd = &cobra.Command{ Short: "Automate all the operations to launch a new network, by collaborating with other in the launch.", Long: `This operation will auto-select the roles, based on a discovered Network shared amongst participants.`, Run: func(cmd *cobra.Command, args []string) { - ipfs, err := bios.NewIPFS(ipfsGatewayAddress, ipfsLocalGatewayAddress) + ipfs, err := bios.NewIPFS(ipfsLocalGatewayAddress, ipfsGatewayAddress) if err != nil { fmt.Println("ipfs client error:", err) os.Exit(1) diff --git a/eos-bios/cmd/root.go b/eos-bios/cmd/root.go index 6d57977..7fd6d61 100644 --- a/eos-bios/cmd/root.go +++ b/eos-bios/cmd/root.go @@ -63,7 +63,7 @@ func init() { RootCmd.PersistentFlags().StringVarP(&cachePath, "cache-path", "", ".eos-bios-cache", "directory to store cached data from discovered network") - for _, flag := range []string{"no-discovery", "cache-path", "my-discovery", "ipfs-api-address", "ipfs-gateway-address"} { + for _, flag := range []string{"no-discovery", "cache-path", "my-discovery", "ipfs-local-gateway-address", "ipfs-gateway-address"} { viper.BindPFlag(flag, RootCmd.Flags().Lookup(flag)) } } diff --git a/bitcoinclock.go b/ethclock.go similarity index 62% rename from bitcoinclock.go rename to ethclock.go index ded50e0..32c94db 100644 --- a/bitcoinclock.go +++ b/ethclock.go @@ -9,7 +9,7 @@ import ( "regexp" ) -func PollBitcoinClock(blockheight int) (blockhash string, err error) { +func PollEthereumClock(blockheight int) (blockhash string, err error) { // TODO: implement more sources, so we are distributed.. add the // possibility to paste the blockhash in case all sites are down? // do a direct connection to the Bitcoin blockchain using a swarm @@ -20,13 +20,46 @@ func PollBitcoinClock(blockheight int) (blockhash string, err error) { //useSite := 0 switch useSite { case 0: - return bitcoinPollMethodBlockchainInfo(blockheight) + return etherscanPollMethod(blockheight) case 1: - return bitcoinPollMethodBlockExplorer(blockheight) + return etherchainPollMethod(blockheight) } panic("hmm.. change the rand.Int modulo up here friend.. or the cases") } +func etherscanPollMethod(height int) (blockhash string, err error) { + var cnt string + cnt, err = getURL(fmt.Sprintf("https://etherscan.io/block/%d", height)) + if err != nil { + return + } + + m := regexp.MustCompile(`>  Hash:\n\n\n0x([0-9a-f]{64})\n`).FindStringSubmatch(cnt) + if m == nil { + // err = fmt.Errorf("regexp didn't match content on blockchain.info") + return + } + + return m[1], nil +} + +func etherchainPollMethod(height int) (blockhash string, err error) { + // TODO: Fix this + var cnt string + cnt, err = getURL(fmt.Sprintf("https://etherchain.io/block/%d", height)) + if err != nil { + return + } + + m := regexp.MustCompile(`>  Hash:\n\n\n0x([0-9a-f]{64})\n`).FindStringSubmatch(cnt) + if m == nil { + // err = fmt.Errorf("regexp didn't match content on blockchain.info") + return + } + + return m[1], nil +} + func bitcoinPollMethodBlockchainInfo(height int) (blockhash string, err error) { var cnt string cnt, err = getURL(fmt.Sprintf("https://blockchain.info/block-height/%d", height)) diff --git a/bitcoinclock_test.go b/ethclock_test.go similarity index 69% rename from bitcoinclock_test.go rename to ethclock_test.go index dfe181d..629b902 100644 --- a/bitcoinclock_test.go +++ b/ethclock_test.go @@ -22,4 +22,15 @@ func TestBitcoinClock(t *testing.T) { hash, err = bitcoinPollMethodBlockExplorer(10000000) assert.NoError(t, err) assert.Equal(t, "", hash) + +} + +func TestEthereumClock(t *testing.T) { + hash, err := etherscanPollMethod(5544735) + assert.NoError(t, err) + assert.Equal(t, "0552171fbbd84d6fc7ee2371b2de61371d9a291aa5ce96521d4f595363f7eee9", hash) + + hash, err = etherscanPollMethod(1000000000) + assert.NoError(t, err) + assert.Equal(t, "", hash) } diff --git a/genesis.go b/genesis.go index f91427d..5a99c63 100644 --- a/genesis.go +++ b/genesis.go @@ -1,7 +1,45 @@ package bios +import ( + "encoding/base64" + "encoding/json" + "errors" + "strings" +) + +// TODO: update with latest GenesisJSON with the basic parameters... type GenesisJSON struct { InitialTimestamp string `json:"initial_timestamp"` InitialKey string `json:"initial_key"` InitialChainID string `json:"initial_chain_id"` } + +func readGenesisData(text string, ipfs *IPFS) (out *GenesisJSON, err error) { + // try base64 encoded genesis + text = strings.TrimSpace(text) + if strings.HasPrefix(text, "ey") { + // base64 decode + decoded, err := base64.RawStdEncoding.DecodeString(text) + if err != nil { + return nil, err + } + text = string(decoded) + } + + if strings.Contains(text, "/ipfs/") { + // fetch from IPFS and decode + cnt, err := ipfs.Get(IPFSRef(text)) + if err != nil { + return nil, err + } + + text = string(cnt) + } + + if strings.HasPrefix(text, "{") { + err = json.Unmarshal([]byte(text), &out) + return + } + + return nil, errors.New("invalid genesis data, not base64-encoded JSON, not JSON, not an ipfs link, what was that anyway?") +} diff --git a/hooks.go b/hooks.go index 0d79257..09797f5 100644 --- a/hooks.go +++ b/hooks.go @@ -1,6 +1,8 @@ package bios import ( + "encoding/base64" + "encoding/json" "fmt" "os" "os/exec" @@ -13,22 +15,43 @@ func (b *BIOS) DispatchInit(operation string) error { }, nil) } -func (b *BIOS) DispatchBootNetwork(genesisJSON, publicKey, privateKey string) error { - return b.dispatch("boot_network", []string{ +func (b *BIOS) DispatchBootPublishGenesis(genesisJSON string) error { + encodedGenesis := base64.RawStdEncoding.EncodeToString([]byte(genesisJSON)) + + return b.dispatch("boot_publish_genesis", []string{ + encodedGenesis, + genesisJSON, + }, nil) +} + +func (b *BIOS) DispatchBootNode(genesisJSON, publicKey, privateKey string) error { + return b.dispatch("boot_node", []string{ genesisJSON, publicKey, privateKey, }, nil) } -func (b *BIOS) DispatchJoinNetwork(kickstart *KickstartData, peerDefs []*Peer, otherPeers []string) error { +func (b *BIOS) DispatchBootConnectMesh(otherPeers []string) error { + return b.dispatch("boot_connect_mesh", []string{ + "p2p-peer-address = " + strings.Join(otherPeers, "\np2p-peer-address = "), + strings.Join(otherPeers, ","), + }, nil) +} + +func (b *BIOS) DispatchJoinNetwork(genesis *GenesisJSON, peerDefs []*Peer, otherPeers []string) error { var names []string for _, peer := range peerDefs { names = append(names, peer.AccountName()) } + cnt, err := json.Marshal(genesis) + if err != nil { + return err + } + return b.dispatch("join_network", []string{ - kickstart.GenesisJSON, + string(cnt), "p2p-peer-address = " + strings.Join(otherPeers, "\np2p-peer-address = "), strings.Join(otherPeers, ","), "producer-name = " + strings.Join(names, "\nproducer-name = "), @@ -36,9 +59,10 @@ func (b *BIOS) DispatchJoinNetwork(kickstart *KickstartData, peerDefs []*Peer, o }, nil) } -func (b *BIOS) DispatchPublishKickstartData(kickstartData string) error { - return b.dispatch("publish_kickstart_data", []string{ - kickstartData, +func (b *BIOS) DispatchBootPublishHandoff() error { + return b.dispatch("boot_publish_handoff", []string{ + b.EphemeralPrivateKey.PublicKey().String(), + b.EphemeralPrivateKey.String(), }, nil) } diff --git a/ipfs.go b/ipfs.go index 00cdc43..ef7d711 100644 --- a/ipfs.go +++ b/ipfs.go @@ -1,11 +1,11 @@ package bios import ( + "errors" "fmt" "io/ioutil" "net/http" "net/url" - "os" ) type IPFS struct { @@ -43,19 +43,17 @@ func (i *IPFS) Get(ref IPFSRef) ([]byte, error) { return nil, err } - reqs := []*http.Request{req1, req2} - var resp *http.Response - for _, req := range reqs { - fmt.Println("Fetching from:", req.URL.String()) - resp, err = i.Client.Do(req) + fmt.Printf("Fetching %q from primary location (%q)...", ref, i.GatewayAddressURL.String()) + resp, err := i.Client.Do(req1) + if err != nil { + fmt.Printf(" failed (%s), trying fallback (%q)...", err, i.FallbackGatewayAddressURL) + + resp, err = i.Client.Do(req2) if err != nil { - fmt.Fprintf(os.Stderr, "NOTE: %q unavailable (%s), trying fallback\n", req.URL.String(), err) - continue + fmt.Println(" failed") + return nil, errors.New("download attempts failed") } } - if err != nil { - return nil, fmt.Errorf("gateway reqs failed: %s", err) - } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) diff --git a/launchdata.go b/launchdata.go index 37c920d..1efb97f 100644 --- a/launchdata.go +++ b/launchdata.go @@ -4,7 +4,7 @@ type IPFSRef string type IPNSRef string type LaunchData struct { - LaunchBitcoinBlock int `json:"launch_bitcoin_block"` + LaunchEthereumBlock int `json:"launch_ethereum_block"` Peers []*PeerLink `json:"peers"` BootSequence IPFSRef `json:"boot_sequence"` Snapshot IPFSRef `json:"snapshot"` diff --git a/network.go b/network.go index ade77e1..8aaf9f3 100644 --- a/network.go +++ b/network.go @@ -21,7 +21,7 @@ type Network struct { MyPeer *Peer - ipfs *IPFS + IPFS *IPFS cachePath string myDiscoveryFile string @@ -34,7 +34,7 @@ type Network struct { func NewNetwork(cachePath string, myDiscoveryFile string, ipfs *IPFS) *Network { return &Network{ - ipfs: ipfs, + IPFS: ipfs, cachePath: cachePath, myDiscoveryFile: myDiscoveryFile, } @@ -189,7 +189,7 @@ func (c *Network) DownloadIPFSRef(ref IPFSRef) error { return nil } - cnt, err := c.ipfs.Get(ref) + cnt, err := c.IPFS.Get(ref) if err != nil { return err } @@ -312,7 +312,7 @@ func (c *Network) verifyGraph() error { func (c *Network) FetchDiscoveryLink(discoveryLink IPNSRef) (out *Discovery, rawDiscovery []byte, err error) { // Resolve recursive the discoveryLink (through /ipns, then /ipfs/Qm.../path to /ipfs/Qmcontent) fmt.Println("Discovery: downloading link", discoveryLink) - rawDiscovery, err = c.ipfs.GetIPNS(discoveryLink) + rawDiscovery, err = c.IPFS.GetIPNS(discoveryLink) if err != nil { return } diff --git a/sample_config/README.md b/sample_config/README.md index 49b8101..6b4c7df 100644 --- a/sample_config/README.md +++ b/sample_config/README.md @@ -18,7 +18,7 @@ Table of contents: instance locally. * When running `boot`, these are executed in order: `hook_init`, - `hook_boot_network`, `hook_publish_kickstart_data` and + `hook_boot_node`, `hook_boot_publish_genesis` and TODO ADD OTHERS HERE `hook_done`. * When running `join`, these are executed in order: `hook_init`, diff --git a/sample_config/hook_boot_connect_mesh.sh b/sample_config/hook_boot_connect_mesh.sh new file mode 100644 index 0000000..f9eb0e7 --- /dev/null +++ b/sample_config/hook_boot_connect_mesh.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# `boot_connect_mesh.sh` hook +# $1 p2p-peer-address statements (like the `join_network` hook) +# $2 comma-separated peer address list +# +# This hook is called when you 'eos-bios boot' or are seleted as BIOS +# Boot in an orchestrated launch. +# +# It should connect your boot node + +echo "Adding p2p-peer-address'es to config.ini" + +echo $1 >> config.ini + + +echo "Restarting boot node" + +docker stop nodeos-bios +docker start nodeos-bios + +sleep 2 diff --git a/sample_config/hook_boot_network.sh b/sample_config/hook_boot_node.sh similarity index 92% rename from sample_config/hook_boot_network.sh rename to sample_config/hook_boot_node.sh index f31694e..4adf4d7 100755 --- a/sample_config/hook_boot_network.sh +++ b/sample_config/hook_boot_node.sh @@ -1,6 +1,6 @@ #!/bin/bash -# `boot_network` hook +# `boot_node` hook # $1 genesis JSON # $2 ephemeral public key # $3 ephemeral private key @@ -33,5 +33,4 @@ echo "" echo " View logs with: docker logs -f nodeos-bios" echo "" -echo "Waiting for nodeos to launch through Docker" -sleep 3 +echo "Waiting 3 secs for nodeos to launch through Docker" diff --git a/sample_config/hook_boot_publish_genesis.sh b/sample_config/hook_boot_publish_genesis.sh new file mode 100644 index 0000000..f971c36 --- /dev/null +++ b/sample_config/hook_boot_publish_genesis.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# `boot_publish_genesis` hook +# $1 = base64 encoded genesis json content +# $2 = raw genesis JSON content + +echo "Please publish this genesis data to everyone:" +echo "" +echo " $1" +echo "" + +# +# You can also publish it through ipfs: +# +#echo $2 | ./ipfs add +# +# and publish the resulting URL +# + +read diff --git a/sample_config/hook_boot_publish_handoff.sh b/sample_config/hook_boot_publish_handoff.sh new file mode 100755 index 0000000..4850da9 --- /dev/null +++ b/sample_config/hook_boot_publish_handoff.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# `boot_publish_handoff` hook +# $1 = public key +# $2 = private key, proving you haven't kept any access to yourself. + +echo "Publish this private key out in the world, to prove you have not kept any access for yourself:" +echo "" +echo " Public key: $1" +echo " Private key: $2" +echo "" + +# +# You can also publish the private key to IPFS and share the ipfs ref: +# echo "Network boot handoff of ephemeral keys: $1 $2" | ./ipfs add +# + +read diff --git a/sample_config/hook_join_network.sh b/sample_config/hook_join_network.sh index b039b56..01bfecb 100755 --- a/sample_config/hook_join_network.sh +++ b/sample_config/hook_join_network.sh @@ -41,5 +41,5 @@ echo "" echo " View logs with: docker logs -f nodeos-bios" echo "" -echo "Waiting for nodeos to launch through Docker" +echo "Waiting 3 secs for nodeos to launch through Docker" sleep 3 diff --git a/sample_config/hook_publish_kickstart_data.sh b/sample_config/hook_publish_kickstart_data.sh deleted file mode 100755 index ca0ee65..0000000 --- a/sample_config/hook_publish_kickstart_data.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -# `publish_kickstart_data` hook -# $1 = base64-encoded kickstart data - -echo "Publish the kickstart data to some fancy place, like Keybase KBFS?" diff --git a/sample_config/my_discovery_file.yaml b/sample_config/my_discovery_file.yaml index d07c81b..d377c58 100644 --- a/sample_config/my_discovery_file.yaml +++ b/sample_config/my_discovery_file.yaml @@ -74,12 +74,12 @@ eosio_appointed_block_producer_signing_key: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8 # This section is required for an `eos-bios` launch. launch_data: # --- WHEN --- - # The bitcoin block is the seed data we're waiting to launch the + # The Ethereum block is the seed data we're waiting to launch the # `orchestrate` step. When you start `eos-bios orchestrate`, it - # first fetches the Bitcoin block's merkle root, and uses it for + # first fetches the Ethereum block's hash, and uses it for # randomly selected the BIOS Boot node. - launch_bitcoin_block: 520781 # Past block, for testing - #launch_bitcoin_block: 525273 # Approx June 2st 2018, 0100 UTC, 2 hours after snapshot freeze + launch_ethereum_block: 5544732 # Past block, for testing + #launch_ethereum_block: 5716358 # Approx June 2st 2018, 0100 UTC, 2 hours after snapshot freeze # --- WITH WHO --- # diff --git a/utils.go b/utils.go index 249fcac..24aded7 100644 --- a/utils.go +++ b/utils.go @@ -28,6 +28,11 @@ func ScanLinesUntilBlank() (out string, err error) { } } +func ScanSingleLine() (out string, err error) { + reader := bufio.NewReader(os.Stdin) + return reader.ReadString('\n') +} + // AN is a shortcut to create an AccountName var AN = eos.AN