diff --git a/README.md b/README.md index 321e099..b84c1f3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # votingRelay dVote library for Relay + +With application running, you can submit fake votes with a json request of the form: +curl -H 'Content-Type: application/json'e":"package","Nonce":"bm9uY2U=","KeyProof":"cHJvb2Y=","Package":"dm90ZXBhY2thZ2U=","Timestamp":"2018-12-14T15:04:05Z"}' http://localhost:8080/submit diff --git a/batch/batch.go b/batch/batch.go new file mode 100644 index 0000000..f69387f --- /dev/null +++ b/batch/batch.go @@ -0,0 +1,84 @@ +package batch + +import ( + "fmt" + "github.com/vocdoni/dvote-relay/types" + "github.com/vocdoni/dvote-relay/db" + "encoding/json" +) + +var rdb *db.LevelDbStorage +var bdb *db.LevelDbStorage +var BatchSignal chan bool +var BatchSize int +var err error + +func Setup(l *db.LevelDbStorage) { + rdb = l.WithPrefix([]byte("relay_")) + bdb = l.WithPrefix([]byte("batch_")) +} + + +//add (queue for counting) +func Add(ballot types.Ballot) error { + //this is probably adding [] + //err := bdb.Put(fmt.Sprintf("%v", p.Nullifier)),[]byte(fmt.Sprintf("%v", p))) + var b []byte + var n []byte + var err error + b, err = json.Marshal(ballot) + n, err = json.Marshal(ballot.Nullifier) + err = bdb.Put(n,b) + if err != nil { + return err + } + + //this actually needs to see if it was added + if bdb.Count() >= BatchSize { + BatchSignal <- true + } + return nil +} + +//create (return batch) +//k is []byte 'batch_' + nullifier +//v is []byte package +//returns slice of nullifiers, batch json +func Fetch() ([]string, []string) { + var n []string + var b []string + iter := bdb.Iter() + for iter.Next() { + k := iter.Key() + v := iter.Value() + err := iter.Error() + if err != nil { + panic(err) + } + n = append(n, string(k[6:])) + b = append(b, string(v)) + } + iter.Release() +// jn, err := json.Marshal(n) +// if err != nil { +// panic(err) +// } +// jb, err := json.Marshal(b) +// if err != nil { +// panic(err) +// } + return n, b +} + +//move from bdb to rdb once pinned +func Compact(n []string) { + for _, k := range n { + //fmt.Println(k) + v, err := bdb.Get([]byte(k)) + if err != nil { + fmt.Println(err.Error()) + } + rdb.Put([]byte(k), v) + bdb.Delete([]byte(k)) + } +} diff --git a/batch/batch_test.go b/batch/batch_test.go new file mode 100644 index 0000000..389c4ab --- /dev/null +++ b/batch/batch_test.go @@ -0,0 +1 @@ +package batch diff --git a/data/data.go b/data/data.go new file mode 100644 index 0000000..c48997e --- /dev/null +++ b/data/data.go @@ -0,0 +1,63 @@ +package data + +import ( + "os" + "fmt" + "bytes" + "io/ioutil" + shell "github.com/ipfs/go-ipfs-api" +) + +func Publish(object []byte) string { + sh := shell.NewShell("localhost:5001") + cid, err := sh.Add(bytes.NewBuffer(object)) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %s", err) + os.Exit(1) + } + return cid +} + +func Pin(path string) { + sh := shell.NewShell("localhost:5001") + err := sh.Pin(path) + if err != nil{ + fmt.Fprintf(os.Stderr, "error: %s", err) + os.Exit(1) + } +} + + +func Retrieve(hash string) []byte { + sh := shell.NewShell("localhost:5001") + reader, err := sh.Cat(hash) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %s", err) + os.Exit(1) + } + content, err := ioutil.ReadAll(reader) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %s", err) + os.Exit(1) + } + return content +} + +func PsSubscribe(topic string) *shell.PubSubSubscription { + sh := shell.NewShell("localhost:5001") + sub, err := sh.PubSubSubscribe(topic) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %s", err) + os.Exit(1) + } + return sub +} + +func PsPublish(topic, data string) { + sh := shell.NewShell("localhost:5001") + err := sh.PubSubPublish(topic, data) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %s", err) + os.Exit(1) + } +} diff --git a/data/data_test.go b/data/data_test.go new file mode 100644 index 0000000..60b49b0 --- /dev/null +++ b/data/data_test.go @@ -0,0 +1,34 @@ +package data + +import ( + "testing" + "fmt" + "encoding/json" + "strings" +) + +func TestPublishAndRetrieve(t *testing.T) { + t.Log("Testing adding json") + + exampleVote := votePacket{ + 000001, + "12309801002", + "nynnynnnynnnyy", + "132498-0-02103908", + } + + testObject, err := json.Marshal(exampleVote) + if err != nil { + t.Errorf("Bad test JSON: %s", err) + } + prepub := string(testObject) + + hash := publish(testObject) + content := retrieve(hash) + postpub := string(content) + //fmt.Println(hash) + //fmt.Println(string(content)) + if strings.Compare(prepub,postpub) != 0 { + t.Errorf("Published file doesn't match. Expected:\n %s \n Got: \n %s \n", prepub, postpub) + } +} diff --git a/db/leveldb.go b/db/leveldb.go new file mode 100644 index 0000000..2204af0 --- /dev/null +++ b/db/leveldb.go @@ -0,0 +1,113 @@ +package db + +// modified from https://github.com/iden3/go-iden3/blob/master/db/leveldb.go + +import ( + "encoding/json" + + "github.com/syndtr/goleveldb/leveldb" +// "github.com/syndtr/goleveldb/leveldb/errors" + "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/syndtr/goleveldb/leveldb/util" + "github.com/syndtr/goleveldb/leveldb/iterator" +) + + +type LevelDbStorage struct { + ldb *leveldb.DB + prefix []byte +} + +func NewLevelDbStorage(path string, errorIfMissing bool) (*LevelDbStorage, error) { + o := &opt.Options{ + ErrorIfMissing: errorIfMissing, + } + ldb, err := leveldb.OpenFile(path, o) + if err != nil { + return nil, err + } + return &LevelDbStorage{ldb, []byte{}}, nil +} + +type storageInfo struct { + KeyCount int +} + +func (l *LevelDbStorage) Count() int { + + keycount := 0 + db := l.ldb + iter := db.NewIterator(util.BytesPrefix(l.prefix), nil) + for iter.Next() { + keycount++ + } + iter.Release() + if err := iter.Error(); err != nil { + panic(err) + } + return keycount +} + +func (l *LevelDbStorage) Info() string { + + keycount := 0 + db := l.ldb + iter := db.NewIterator(util.BytesPrefix(l.prefix), nil) + for iter.Next() { + keycount++ + } + iter.Release() + if err := iter.Error(); err != nil { + return err.Error() + } + json, _ := json.MarshalIndent( + storageInfo{keycount}, + "", " ", + ) + return string(json) +} + +func (l *LevelDbStorage) WithPrefix(prefix []byte) *LevelDbStorage { + return &LevelDbStorage{l.ldb, append(l.prefix, prefix...)} +} + +func (l *LevelDbStorage) Get(key []byte) ([]byte, error) { + v, err := l.ldb.Get(append(l.prefix, key[:]...), nil) + if err != nil { + return nil, err + } + return v, err +} + +func (l *LevelDbStorage) Put(key []byte, value []byte) error { + err := l.ldb.Put(append(l.prefix, key[:]...), value, nil) + if err != nil { + return err + } + return nil +} + +func (l *LevelDbStorage) Delete(key []byte) error { + err := l.ldb.Delete(append(l.prefix, key[:]...), nil) + if err != nil { + return err + } + return nil +} + +func (l *LevelDbStorage) Close() { + if err := l.ldb.Close(); err != nil { + panic(err) + } +} + +func (l *LevelDbStorage) Iter() iterator.Iterator { + db := l.ldb + i := db.NewIterator(util.BytesPrefix(l.prefix), nil) + return i +} + +func (l *LevelDbStorage) LevelDB() *leveldb.DB { + return l.ldb +} + diff --git a/generator/generator.go b/generator/generator.go new file mode 100644 index 0000000..e51f1eb --- /dev/null +++ b/generator/generator.go @@ -0,0 +1,104 @@ +package main + +import ( + "os" + "strconv" + "time" + "fmt" + "math/rand" + "encoding/json" + "encoding/base64" + "net/http" + "bytes" + "io/ioutil" + "github.com/vocdoni/dvote-relay/types" +) + +func makeBallot() string { + var bal types.Ballot + + bal.Type = "ballot0" + + pidBytes := make([]byte, 32) + rand.Read(pidBytes) + bal.PID = base64.StdEncoding.EncodeToString(pidBytes) + + nullifier := make([]byte, 32) + rand.Read(nullifier) + bal.Nullifier = nullifier + + vote := make([]byte, 32) + rand.Read(vote) + bal.Vote = vote + + franchise := make([]byte, 32) + rand.Read(franchise) + bal.Franchise = franchise + + + b, err := json.Marshal(bal) + if err != nil { + fmt.Println(err) + return "error" + } + //todo: add encryption, pow + return string(b) +} + +func makeEnvelope(ballot string) string { + var env types.Envelope + + env.Type = "envelope0" + + env.Nonce = rand.Uint64() + + kp := make([]byte, 4) + rand.Read(kp) + env.KeyProof = kp + + env.Ballot = []byte(ballot) + + env.Timestamp = time.Now() + + e, err := json.Marshal(env) + if err != nil { + fmt.Println(err) + return "error" + } + //todo: add encryption, pow + return string(e) + +} + +func main() { + interval := os.Args[1] + i, _ := strconv.Atoi(interval) + timer := time.NewTicker(time.Millisecond * time.Duration(i)) + rand.Seed(time.Now().UnixNano()) + url := "http://localhost:8090/submit" + fmt.Println("URL:>", url) + + for { + select { + case <- timer.C: + fmt.Println(makeEnvelope(makeBallot())) + var jsonStr = []byte(makeEnvelope(makeBallot())) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) + req.Header.Set("X-Custom-Header", "myvalue") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + fmt.Println("response Status:", resp.Status) + fmt.Println("response Headers:", resp.Header) + body, _ := ioutil.ReadAll(resp.Body) + fmt.Println("response Body:", string(body)) + default: + continue + } + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..fed06bc --- /dev/null +++ b/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "time" + "encoding/gob" + "bytes" + "github.com/vocdoni/dvote-relay/batch" + "github.com/vocdoni/dvote-relay/net" + "github.com/vocdoni/dvote-relay/db" + "github.com/vocdoni/dvote-relay/data" +) + +var dbPath = "~/.dvote/relay.db" +var batchSeconds = 10 //seconds +var batchSize = 3 //packets + +var err error +var batchTimer *time.Ticker +var batchSignal chan bool +var signal bool + + +func main() { + + db, err := db.NewLevelDbStorage(dbPath, false) + if err != nil { + panic(err) + } + defer db.Close() + + batch.Setup(db) + + batchTimer = time.NewTicker(time.Second * time.Duration(batchSeconds)) + batchSignal = make(chan bool) + + batch.BatchSignal = batchSignal + batch.BatchSize = batchSize + + fmt.Println("Entering main loop") + go net.Listen("8090") + for { + select { + case <- batchTimer.C: + fmt.Println("Timer triggered") +// fmt.Println(batch.Create()) + //replace with chain link + case signal := <-batchSignal: + if signal == true { + fmt.Println("Signal triggered") + ns, bs := batch.Fetch() + buf := &bytes.Buffer{} + gob.NewEncoder(buf).Encode(bs) + bb := buf.Bytes() + cid := data.Publish(bb) + data.Pin(cid) + fmt.Printf("Batch published at: %s \n", cid) + // add to ipfs + // add to chain + // announce to pubsub + //fmt.Println("Nullifiers:") + //fmt.Println(n) + //fmt.Println("Batch:") + //fmt.Println(b) + batch.Compact(ns) + } + default: + continue + } + } +} diff --git a/net/net.go b/net/net.go new file mode 100644 index 0000000..1a88021 --- /dev/null +++ b/net/net.go @@ -0,0 +1,64 @@ +package net + +import ( + "encoding/json" + "fmt" + "net/http" + "io" + "github.com/vocdoni/dvote-relay/batch" + "github.com/vocdoni/dvote-relay/types" +) + + +func parse(rw http.ResponseWriter, request *http.Request) { + decoder := json.NewDecoder(request.Body) + + var e types.Envelope + var b types.Ballot + + err := decoder.Decode(&e) + if err != nil { + panic(err) + } + + err = json.Unmarshal(e.Ballot, &b) + if err != nil { + panic(err) + } + + + //check PoW + //check key + //decrypt + //check franchise + //construct packet + + //this should should be randomized, or actually taken from input + //b.PID = "1" + //b.Nullifier = []byte{1,2,3} + //b.Vote = []byte{4,5,6} + //b.Franchise = []byte{7,8,9} + + err = batch.Add(b) + if err != nil { + panic(err) + } + + j, err := json.Marshal(e) + if err != nil { + panic(err) + } + io.WriteString(rw, string(j)) +} + +func Listen(port string) { + http.HandleFunc("/submit", parse) + //add waitgroup + func() { + fmt.Println("serving on " + port) + err := http.ListenAndServe(":" + port, nil) + if err != nil { + panic("ListenAndServe: " + err.Error()) + } + }() +} diff --git a/net/net_test.go b/net/net_test.go new file mode 100644 index 0000000..add58a7 --- /dev/null +++ b/net/net_test.go @@ -0,0 +1,52 @@ +package net + +import ( + "testing" + "encoding/json" + "net/http" + "fmt" + "time" + "bytes" + "io/ioutil" +) + +//func generateSubmission() submission { + +//} + +func TestListen(t *testing.T) { + t.Log("Testing listener") + + testSubmission := submission { + "package", + []byte("012345678"), + []byte("012345678"), + []byte("012345678"), + time.Now(), + } + + listen("8080") + + url := "http://localhost:8080/submit" + fmt.Println("URL:>", url) + + j, err := json.Marshal(testSubmission) + if err != nil { + t.Errorf("Bad test JSON: %s", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(j)) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + t.Errorf("Error in client: %s", err) + } + defer resp.Body.Close() + + fmt.Println("response Status:", resp.Status) + fmt.Println("response Headers:", resp.Header) + body, _ := ioutil.ReadAll(resp.Body) + fmt.Println("response Body:", string(body)) +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..03eee3a --- /dev/null +++ b/types/types.go @@ -0,0 +1,31 @@ +package types + +import ( + "time" +) + +type Ballot struct { + Type string + PID string + Nullifier []byte + Vote []byte + Franchise []byte +} + +type Envelope struct { + Type string + Nonce uint64 + KeyProof []byte + Ballot []byte + Timestamp time.Time +} + +type Batch struct { + Type string + Nullifiers []string + URL string + TXID string + Nonce []byte + Signature string +} +