From 917b59b72034f25e6a10576decddd6539cf9382c Mon Sep 17 00:00:00 2001 From: David Florness Date: Sun, 26 Jul 2020 22:11:25 -0600 Subject: [PATCH] Refactor election info and save election info --- config.go | 136 +++++++++++++++++++++++++++++++++++++++---------- config_test.go | 39 +++++++------- main.go | 60 +++++++++++++++++----- 3 files changed, 176 insertions(+), 59 deletions(-) diff --git a/config.go b/config.go index c7fadf3..2fc9070 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "io" "os" "path" @@ -11,9 +12,25 @@ import ( "github.com/mr-tron/base58/base58" ) -// Used to decode an election in the JSON elections file, which will be a list -// of these. Maps the election key to the election metadata -type RawElections map[string]struct{ +// where cached election info is stored +var ( + tallyardConfigPath = path.Join(xdg.ConfigHome(), "tallyard") + electionInfoFilePath = path.Join(tallyardConfigPath, "elections.json") +) + +// maps an election's key to its cached info +type ElectionInfos map[string]*ElectionInfo + +// an entry of an election's cached info +type ElectionInfo struct{ + PrivKey crypto.PrivKey + PubKey crypto.PubKey +} + +// used for JSON {en,de}coding +type RawElectionInfos map[string]RawElectionInfo + +type RawElectionInfo struct{ // Our private key in base58. TODO: encrypt PrivKey string // Our public key in base58. Kept in addition to the private key to @@ -21,50 +38,115 @@ type RawElections map[string]struct{ PubKey string } -type ElectionConfig struct{ - PrivKey crypto.PrivKey - PubKey crypto.PubKey +// Saves an election's info into a file that can later be easily retrieved, even +// on separate invocations of tallyard. Returns an error if there was one. +func saveElectionInfo(electionKey string, privKey crypto.PrivKey, pubKey crypto.PubKey) error { + err := os.MkdirAll(tallyardConfigPath, 0700) + if err != nil { + return err + } + infos, err := loadPastElectionInfos() + if err != nil { + return fmt.Errorf("unable to load election info while saving election info: %s", err) + } + file, err := os.OpenFile(electionInfoFilePath, + os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("unable to open election info file: %s", err) + } + defer file.Close() + infos[electionKey] = &ElectionInfo{ + PrivKey: privKey, + PubKey: pubKey, + } + raw, err := marshalElectionInfos(infos) + if err != nil { + return fmt.Errorf("unable to marshal election info for caching: %s", err) + } + err = encodeElectionsInfos(file, raw) + if err != nil { + return fmt.Errorf("unable to encode election info for caching: %s", err) + } + return nil } -type ElectionConfigs map[string]*ElectionConfig +// Checks whether we've joined this election before and attempts to retrieve its +// info we cached for it. Useful if a connection was suddently lost and +// tallyard had to restart. Returns the election's info (nil if no such info +// exists) and an error +func getElectionInfo(electionKey string) (*ElectionInfo, error) { + infos, err := loadPastElectionInfos() + if err != nil { + return nil, fmt.Errorf("unable to get election info: %s", err) + } + if info, hasElectionKey := infos[electionKey]; hasElectionKey { + return info, nil + } + return nil, nil +} -// sees if we've joined this election before and attempts to retrieve the data -// we stored for it -func getElectionConfigMaybe(electionKey string) *ElectionConfig { - root := path.Join(xdg.ConfigHome(), "tallyard") - file, err := os.OpenFile(path.Join(root, "elections.json"), os.O_RDONLY|os.O_CREATE, 0600) +func loadPastElectionInfos() (ElectionInfos, error) { + err := os.MkdirAll(tallyardConfigPath, 0700) + if err != nil { + return nil, err + } + file, err := os.OpenFile(electionInfoFilePath, + os.O_RDONLY|os.O_CREATE, 0600) if err != nil { - panic(err) + return nil, fmt.Errorf("unable to open election info file: %s", err) } defer file.Close() - - rawElections, err := decodeElections(file) + raw, err := decodeElectionInfos(file) if err != nil { - panic(err) + return nil, fmt.Errorf("unable to decode election info for caching: %s", err) } - - electionConfigs, err := unmarshalRawElections(rawElections) + infos, err := unmarshalRawElectionInfos(raw) if err != nil { - panic(err) + return nil, fmt.Errorf("unable to encode election info for caching: %s", err) } + return infos, nil +} - if cfg, hasElectionKey := electionConfigs[electionKey]; hasElectionKey { - return cfg - } else { - return nil - } +func encodeElectionsInfos(writer io.Writer, rawElections RawElectionInfos) error { + encoder := json.NewEncoder(writer) + return encoder.Encode(rawElections) } -func decodeElections(reader io.Reader) (rawElections RawElections, err error) { +func decodeElectionInfos(reader io.Reader) (rawElections RawElectionInfos, err error) { decoder := json.NewDecoder(reader) err = decoder.Decode(&rawElections) + if err == io.EOF { + return make(RawElectionInfos), nil + } if err != nil { return nil, err } return rawElections, nil } -func unmarshalRawElections(rawElections RawElections) (electionConfigs ElectionConfigs, err error) { +func marshalElectionInfos(electionConfigs ElectionInfos) (rawElections RawElectionInfos, err error) { + rawElections = make(RawElectionInfos) + for electionKey, electionConfig := range electionConfigs { + privKeyBytes, err := crypto.MarshalPrivateKey(electionConfig.PrivKey) + if err != nil { + return nil, err + } + privKeyStr := base58.Encode(privKeyBytes) + pubKeyBytes, err := crypto.MarshalPublicKey(electionConfig.PubKey) + if err != nil { + return nil, err + } + pubKeyStr := base58.Encode(pubKeyBytes) + rawElections[electionKey] = RawElectionInfo{ + PrivKey: privKeyStr, + PubKey: pubKeyStr, + } + } + return rawElections, nil +} + +func unmarshalRawElectionInfos(rawElections RawElectionInfos) (electionConfigs ElectionInfos, err error) { + electionConfigs = make(ElectionInfos) for electionKey, info := range rawElections { privKeyBytes, err := base58.Decode(info.PrivKey) if err != nil { @@ -82,7 +164,7 @@ func unmarshalRawElections(rawElections RawElections) (electionConfigs ElectionC if err != nil { return nil, err } - electionConfigs[electionKey] = &ElectionConfig{ + electionConfigs[electionKey] = &ElectionInfo{ PrivKey: privKey, PubKey: pubKey, } diff --git a/config_test.go b/config_test.go index 7f8420b..e7a74e9 100644 --- a/config_test.go +++ b/config_test.go @@ -7,60 +7,59 @@ import ( ) const ( - ek0 = "DpgZ3EG8cyNAxrbGejTTUuJB6KkvKVGK9WEpQWDh6RLd0QmVitsctABm8xbadtkqU46WMLCjuAadUR2RAkjaPf7xz7X" - ek1 = "Dmfo6renA9YKuTFdGL4iSP5fcHbxHG4Qc9XTHkBzdHPa0QmVjm2QACdwMYguMtMooDoLsiagyVfe7qwxpuzUmX1stTM" + electionKey0 = "DpgZ3EG8cyNAxrbGejTTUuJB6KkvKVGK9WEpQWDh6RLd0QmVitsctABm8xbadtkqU46WMLCjuAadUR2RAkjaPf7xz7X" + electionKey1 = "Dmfo6renA9YKuTFdGL4iSP5fcHbxHG4Qc9XTHkBzdHPa0QmVjm2QACdwMYguMtMooDoLsiagyVfe7qwxpuzUmX1stTM" ) -var rawElectionsRef = RawElections{ - ek0: +var rawElectionInfosRef = RawElectionInfos{ + electionKey0: { PrivKey: "7r1r8iBFRYi5hLWbNS5vx53fAvnyFFGrgnDEwu8DSGRVdik5TVuPhCBpqummjaMMXgogcoz6h88rEWvM1af5G4VV4t5cNeFF6hXDyM14gxAMBkm2cEgpcf7d3AAH4auiHhZcB5XWUZeGKUgjz2WGtLPChHhAZzCA8GnDZHhrLSL2qfSt1ZYhyyvTLbEvfNZ7VhwXK64Y4UxjaRAXsMpBCret9aAck9pJxJnE5gm91JxTQybcTKULN9BxvJV99kLUdZ66PJ6ff5TZB4oNzShEQcdyP2G8tUJkCujokXWpZQh7ZDE4dtuoP6uvQhYSkPtWTgYv888BRYDoNDDqWc1nqZGqftr5EHogUTC4H7D3UWngVpwd1Mjbi8vS2DicKDXwJqP2bSyeVGLG3yLkWyjfxfBwNwnHGcBmhiSrL6bHfUrpJqy95UPC3ZSJeLNaYEToQAJmCEah4D7BZchF13rXjLGMsXxTUTHt9mMmEa8jdxLbqUkafVsN1VcHeZBVScvnFCS1e4xe1Ja54xg8H7b5HddB8JcSHaW2fJkvTrr3avT24NivAw4nvRg5UasMR2WMzgYP1rkmyV5vyPoJAnytYX8hZp2zX6gPbZBqUEDAQcvAegibjcDhRZMxMLCrR73UReEqXjjFhKYh7AsDz6uNDrwE6oVRJZ9GLBAetQ9HothETXr2PUaFkh82zCCCYbNL16aFWgiKYBAd1iaKL7idA4GrzTXuYR5HHJxcGSXNc9mUrGEEY2tLkNLPk3VHrqEDpzKArA72PGy1mKXVghoaJeZAe5am49ZPuPG4VQnkxHXB8NkhPkc26cbD6iYxq1cC7XDEMrQ8ggvecjhFxVnnFsDE2Ub7cZyVompvckPMX7R7VuERq7Ax1Vk4qNgbP5hHpn5F5DGpRNRdtpsEpzseSvBKQ6nDknSXFYSya99b4T7nwVkYCj7KuD21Xfmj4mRYQxy4aebKggUvPWAmMNSTWdrPK9QF7Y2cwagpDYQx8KEcVgGU5CrCeKscAbc84PbP6H2HMaMB5ivdMmuEbQfJuyMpX7sXwXCvHUg4p8R4UMf7nkF5c76H4oRH2CaJnnQ25DwYtMtfWmidTFFJECtaLU154z47L2TzNtn3G16UtUNvyLBA5b5HBKhKcjzwVHmRBreWz3urCF1obBT49mERb9mrsyicYjfoKqXS1mVVVCkvs1vAEm73RepXBHD37aQ4DVk9ThFB2RMiDxCwaW7EYUkNLpRaH1FHPJg2o9x1EkPsqcTg5TL31e3EctivtyLCVPLsaUsFYFmzyXCsniXeJPdPGxvt32fbeP1GUXv8N2xLAoVg815cuEnGvkjvztLK8Mv2fxtsPGQaEH6D9FEM2x1xRAFj6cspkbC5RpEbTcuEaaXBpBgVZSxT7TkYWUyerSYKLhocacwmGSYgduxvTB5w1nTfbk3sobEEjCrZwZjiQzdwkrP1iG4x1YDSB4f97n7gnbkEs5LDvuDwgS6KBeWnWTw1V2P5U8VkTCFhj6UAiv1FP9Tdi664MgDVNXQoEGQryy98zShcyg3BZ2Jr8yCdri3gVgMLG7b6Q91AXehz4RRs2X6SrZtCLKN2uBmhGwntvL2FvXDkGanXuRfUEMP8g2VdtWuW8", PubKey: "7ySHQR7YZJApfZRKds9hzzFypZo8s6WQyZqXCYVHRjug84TSqhdKgdVBhS3cZ6NYMfygyfPMvAgjnn2mp3c5dSME49opHiD3ioUaArqGqddyWf3gonWVfnjkTnMcdpGXvDt74uAYf2NrwsphN1MuBBX5NAoHtE5vTRMAiZL94KSg7qFNZM1PckSJfo4xJUD1JgmEMqBvavosjFoenhZv7FjLkXFJhHFGfF7KaK4Q62bUubYtgiR1bkYk1RtctkjUvnBW8YnSx1QFGrmA5qAz8ZUCRmeqgktvpYDR2bj3XFDUGmjqNG3N4QMikg2a6vquCuuT6kv5WefzUYncvijQLHx85z74F8wMZV5huUxQM3RS2aZgc89WvFjeP5Pkmv1bUqvdZ5n1Yi3U65b9KBCR9ycL", }, - ek1: + electionKey1: { PrivKey: "B9bLmHeKLQU1hX23meSn2kJP4wwPAGeaXRpYnkZyKfR98RYs51wo7syA9N7oSbcf49hyrp9vaFpo2GA9yYkiWsPh3smeDDbSZsuzNoAZXr4tjPFytaaddvbsbsme5YbdMejRh1qPncRR6vmhBwb9r4xZBugb2PZPNbqBKKkVotF3YzVzJ3s6kjKyTirGW8xowFbma8hwWntfaNVuZF143BeHQMjyAB5QYRtLtx85RS4Zkxm4wv71Y5ZAcf3VMgmjGXTVXivWX4pkDHBSYfXYr6CoqLy4iNNWstnehBVPAJFM9YvYTnoT3DYXyXXgBSnfPLXFgX9yxkKh8YtXVNKewLqa3b9uKHzCh1FbxTEThfTK1Q4YPFLEek8eR2DUHinGuPrEgCmH4H8ZKXmfLkmGTgHWDkzDr4u2e74jE7Fz9XWmwHq39ZzUsE5SFVWVm6domENEgQMPHU83X7RFXPwgfCCiNdNYkozL9hvBgPujDtiiyBPExbUqY5dnCceTuD6ysJmhwL4SHWpdHKZwwWETWeDTzyA7snZjbebcmE1visFydZ2UQaQdHKnNU5gV2NkiQupyyCEvs9rrHW6xf6g9myMrzdRQVRBBuKswCjq91CNA2uAtkyNLzEhD9GSLuGFRgmtzEwGxek2p12ysUjJyfgBDY28CoMJVJfeK8dyCHQnyvtmA32uxQ8m83QDRxTGY52ubsLaKm9n1L4MiJKW73BC8JoZxdQmuW8278JUAWdTK7KDk2yCDfFfUwGLq6Jz1aTqV5KM23Ysjto6ENeYSHiNrwJ4yBCP2cq4bs48dfdCqWodzBN2HaUpiFiPfQDG5r52zow1aULEaLeUrUbCChKTi6WqWyEBKxEpVMiL7dRQqzHyq3JAFyu51CgmbfFyQtamV8ULE4aVMotrCm2QcrvWxLxkjvSb4cCcnJwyQ9QyT8jFpfVnDWSdLSpbRAxJzr4c4pxWMDjhhPfQYWLArMTv7N6woQwMg8L6AJVbm96qQQ8v99VHVRzib4gVP36DYmTT8WEks2LtFXBwypU8x17iREvwex2ZXHQgvYRAkgw1uYg8rkGMQ5ZTrekTbbsBnUxmcV7Y5Cb18PUVeTRvDtFUB8K2VsZZeTnfWsfvKNqURs4nLyn4VPicgtUbDwLvJnpijDfodv6ydYfbdpDEtkGrKXKwNTwWZPFAts61Wnsouu7dLCN2gKWfhh8x4NdcnZP3fKE15GfLViFMeanAZvww2DVyW78cXpeztanCmkbUqLSvoGkRJsZ49pFEtVeGv1at6SFNKYzyPCWYnegfvs2f54rUKCchWDoNZwYw7fja32d87qEUzWouorLXc4ATkykaKzHthoiwEMjweQKGgGgUXbNbP56x1jZYgLPSCVHHgGaLNKgqW6tSXEKThEEg42kzDNiymr7soWwGrZPsNyE5vpfoxd7ebeKy2LJ4H9AwqAbUFhuFAFwzcTRLsiWGJKg9ysi8fpcGBcg7NbrrkHwsR1jvvpV7f855pRTPMXCUQpYiGWVPtJ1UZAsmX5RqVmgfdyJHYok8aL2XML9GeHTHZZNg7iD2ST8DSdKNhaYs7oxyTMvWsWnqPezUq5kaQxRa8zTkFqUJYUAgiZrKDFrgsXC4EgboyZM3CK", PubKey: "7ySHQR7YZJApfZRKds9hzzFypZo8s6WQyZqXCYVHRjug84TSqhdKDnnZpz69WtL8kvTgtrjEF3GHmsfjgMSt2t7vYgU26qHZfCXPUhA9u93vkCXSG6ZLBWw8K2HxMWPR69LoWzCsfXnLnw9mbr64uHmEeP64PSidtyshApUS73ikt7KGMdk3twJLr7SZ8uaqw6DCvBd9po5tVtPZ9gS1NPG3vuavLfm2P9YT2LMzCf68bJmqzgiVpUxRemvpwppVP675C5m6ctDELBTvqwQ9dc8baG4ztZPehEeUhULG5JpgtMKayCYRNhQVKdENyWXfFYzpMJ27iMH3LT2dixjgXRkqh8PKFt1srJA4we6vCLx1yjDjJV7MELo9P9nkXbojjQLXnqa41bYYGMN4ChK1C78U", }, } -func TestRawElectionDecoding(t *testing.T) { +func TestRawElectionInfosDecoding(t *testing.T) { jsonStream := fmt.Sprintf(` {"%s": {"PrivKey": "%s", "PubKey": "%s"}, "%s": {"PrivKey": "%s", "PubKey": "%s"}}`, - ek0, - rawElectionsRef[ek0].PrivKey, - rawElectionsRef[ek0].PubKey, - ek1, - rawElectionsRef[ek1].PrivKey, - rawElectionsRef[ek1].PubKey, + electionKey0, + rawElectionInfosRef[electionKey0].PrivKey, + rawElectionInfosRef[electionKey0].PubKey, + electionKey1, + rawElectionInfosRef[electionKey1].PrivKey, + rawElectionInfosRef[electionKey1].PubKey, ) - rawElections, err := decodeElections(strings.NewReader(jsonStream)) + rawElections, err := decodeElectionInfos(strings.NewReader(jsonStream)) if err != nil { panic(err) } - if len(rawElections) != 2 { t.FailNow() } - if _, exists := rawElections[ek0]; !exists { + if _, exists := rawElections[electionKey0]; !exists { t.FailNow() } - if _, exists := rawElections[ek1]; !exists { + if _, exists := rawElections[electionKey1]; !exists { t.FailNow() } - if rawElections[ek0].PrivKey != rawElectionsRef[ek0].PrivKey { + if rawElections[electionKey0].PrivKey != rawElectionInfosRef[electionKey0].PrivKey { t.FailNow() } - if rawElections[ek1].PrivKey != rawElectionsRef[ek1].PrivKey { + if rawElections[electionKey1].PrivKey != rawElectionInfosRef[electionKey1].PrivKey { t.FailNow() } - if rawElections[ek0].PubKey != rawElectionsRef[ek0].PubKey { + if rawElections[electionKey0].PubKey != rawElectionInfosRef[electionKey0].PubKey { t.FailNow() } - if rawElections[ek1].PubKey != rawElectionsRef[ek1].PubKey { + if rawElections[electionKey1].PubKey != rawElectionInfosRef[electionKey1].PubKey { t.FailNow() } } diff --git a/main.go b/main.go index 175d3b9..58017c5 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,7 @@ const protocolID = protocol.ID("/tallyard/0.0.0") var logger = log.Logger("tallyard") -func bootstrap(election *Election, me *Me, merkleRoot []byte, hostOpts ...libp2p.Option) { +func bootstrap(election *Election, me *Me, merkleRoot []byte, electionKey string, hostOpts ...libp2p.Option) { me.ctx = context.Background() election.remoteVoters = make(map[peer.ID]*Voter) @@ -67,7 +67,17 @@ func bootstrap(election *Election, me *Me, merkleRoot []byte, hostOpts ...libp2p if election.masterID == "" { // we are the master fmt.Println("share this with peers:") - fmt.Printf("%s0%s\n", base58.Encode(merkleRoot), me.ID()) + electionKey = fmt.Sprintf("%s0%s", base58.Encode(merkleRoot), me.ID()) + fmt.Printf("%s\n", electionKey) + + err := saveElectionInfo( + electionKey, + me.Peerstore().PrivKey(me.ID()), + me.Peerstore().PubKey(me.ID()), + ) + if err != nil { + panic(err) + } logger.Info("waiting for incoming streams and finding voters...") @@ -83,7 +93,7 @@ func bootstrap(election *Election, me *Me, merkleRoot []byte, hostOpts ...libp2p fmt.Println("press ENTER to solidify group of voters and start voting") stdReader := bufio.NewReader(os.Stdin) - _, err := stdReader.ReadString('\n') + _, err = stdReader.ReadString('\n') if err != nil { panic(err) } @@ -107,6 +117,15 @@ func bootstrap(election *Election, me *Me, merkleRoot []byte, hostOpts ...libp2p } election.RUnlock() } else { // we are a slave + err := saveElectionInfo( + electionKey, + me.Peerstore().PrivKey(me.ID()), + me.Peerstore().PubKey(me.ID()), + ) + if err != nil { + panic(err) + } + logger.Info("attempting to open stream with master peer...") stream, err := me.NewStream(me.ctx, election.masterID, protocolID) rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream)) @@ -163,9 +182,11 @@ func bootstrap(election *Election, me *Me, merkleRoot []byte, hostOpts ...libp2p func main() { log.SetAllLoggers(log.LevelWarn) - debug := flag.Bool("v", false, "enable verbose logging for debugging") + verbose := flag.Bool("v", false, "enable verbose logging for debugging") + master := flag.Bool("m", false, "indicate that this node is the " + + "master and the candidates are given via positional arguments") flag.Parse() - if *debug { + if *verbose { log.SetLogLevel("tallyard", "info") } else { log.SetLogLevel("dht", "critical") @@ -181,8 +202,19 @@ func main() { merkleRoot []byte ) - // check if election key was given via CLI args - if electionKey = flag.Arg(0); electionKey != "" { + if *master { + // we are the master and the candidates are the positional + // arguments + election = new(Election) + for _, candidate := range flag.Args() { + election.candidates = append( + election.candidates, + Candidate(candidate), + ) + } + } else if electionKey = flag.Arg(0); electionKey != "" { + // we are a slave slave the and election key was given via CLI + // args election = new(Election) merkleRoot, election.masterID = splitElectionKey(electionKey) } else { @@ -196,18 +228,22 @@ func main() { if electionKey != "" { // we are a slave // try to recover election info if we've joined before - if electionConfig := getElectionConfigMaybe(electionKey); electionConfig != nil { + electionInfo, err := getElectionInfo(electionKey) + if err != nil { + panic(err) + } + if electionInfo != nil { // ensure public key from config is same as one derived // from private key - if !electionConfig.PrivKey.GetPublic().Equals(electionConfig.PubKey) { + if !electionInfo.PrivKey.GetPublic().Equals(electionInfo.PubKey) { // TODO: handle properly - panic("public keys do not match") + panic("loaded public key does not match derived one from private key") } - identityOpt = libp2p.Identity(electionConfig.PrivKey) + identityOpt = libp2p.Identity(electionInfo.PrivKey) } } - bootstrap(election, me, merkleRoot, identityOpt) + bootstrap(election, me, merkleRoot, electionKey, identityOpt) startVoting(election, me) } -- 2.38.4