fix balance check in server logic
[epoint] / pkg / server / server.go
index ab41250..8be10b2 100644 (file)
@@ -5,11 +5,11 @@ package server
 import (
        "bytes"
        "crypto/openpgp"
-       "crypto/rand"
        "epoint/document"
        "epoint/key"
        "epoint/store"
        "fmt"
+       "log"
        "time"
 )
 
@@ -33,7 +33,7 @@ type work struct {
 var db *store.Conn
 var serverkey *openpgp.Entity
 var newchan chan *work
-var delchan chan string
+var delchan chan *work
 
 type worklist struct {
        head *work
@@ -64,31 +64,19 @@ func popwork(ws *worklist) *work {
 
 func setserverkey(e *openpgp.Entity) (err error) {
        serverkey = e
-
-       // TODO: maybe Serialize should do this internally
-       for _, ident := range e.Identities {
-               err = ident.SelfSignature.SignUserId(rand.Reader, ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
-               if err != nil {
-                       return
-               }
-       }
-       for _, subkey := range e.Subkeys {
-               err = subkey.Sig.SignKey(rand.Reader, subkey.PublicKey, e.PrivateKey)
-               if err != nil {
-                       return
-               }
+       err = key.SelfSign(e)
+       if err != nil {
+               return
        }
-
-       b := new(bytes.Buffer)
-       err = e.Serialize(b)
+       d, err := key.Format(e)
        if err != nil {
                return
        }
-       err = db.Set("key", key.Id(e), b.Bytes())
+       err = db.Set("key", key.Id(e), d)
        if err != nil {
                return
        }
-       err = db.Set("", "serverkey", b.Bytes())
+       err = db.Set("", "serverkey", d)
        return
 }
 
@@ -263,6 +251,7 @@ func addDraft(d []byte, signed *document.Signed, draft *document.Draft) (c []byt
        w.signed = signed
        w.draft = draft
        w.sync = make(chan int)
+       log.Printf("add draft work: %s", w.account)
        newchan <- w
        <-w.sync
        return w.out, w.err
@@ -271,21 +260,24 @@ func addDraft(d []byte, signed *document.Signed, draft *document.Draft) (c []byt
 func addDebit(d []byte, signed *document.Signed, cert *document.DebitCert) (c []byte, err error) {
        w := new(work)
        w.docid = document.Id(signed)
-       w.account = fmt.Sprintf("%s.%s", cert.Holder, cert.Issuer)
+       w.account = fmt.Sprintf("%s.%s", cert.Beneficiary, cert.Issuer)
        w.in = d
        w.signed = signed
        w.debit = cert
        w.sync = make(chan int)
+       log.Printf("add debit work: %s", w.account)
        newchan <- w
        <-w.sync
        return w.out, w.err
 }
 
 func dispatch() {
+       log.Printf("start dispatch")
        works := make(map[string]*worklist)
        for {
                select {
                case w := <-newchan:
+                       log.Printf("queue work: %s", w.account)
                        ws := works[w.account]
                        if ws == nil {
                                // TODO: unnecessary alloc
@@ -294,58 +286,57 @@ func dispatch() {
                        } else {
                                pushwork(ws, w)
                        }
-               case account := <-delchan:
-                       ws := works[account]
-                       w := popwork(ws)
-                       if w == nil {
-                               delete(works, account)
+               case w := <-delchan:
+                       log.Printf("unqueue work: %s", w.account)
+                       ws := works[w.account]
+                       wnext := popwork(ws)
+                       if wnext == nil {
+                               delete(works, w.account)
                        } else {
-                               go handle(w)
+                               go handle(wnext)
                        }
                }
        }
 }
 
 func handle(w *work) {
+       log.Printf("start work: %s", w.account)
        if w.debit != nil {
-               handleDebit(w)
+               w.out, w.err = handleDebit(w)
        } else if w.draft != nil {
-               handleDraft(w)
+               w.out, w.err = handleDraft(w)
        } else {
                panic("unreachable")
        }
-       delchan <- w.account
+       log.Printf("finish work: %s outlen: %d", w.account, len(w.out))
+       delchan <- w
        w.sync <- 0
 }
 
-func handleDraft(w *work) {
+func handleDraft(w *work) (c []byte, err error) {
        nonce := fmt.Sprintf("%s.%s", w.account, w.draft.Nonce)
        oldid, err := db.Get("draftby/key.issuer.nonce", nonce)
        if err == nil {
                if string(oldid) != w.docid {
-                       w.err = fmt.Errorf("draft nonce is not unique (see draft %s)", oldid)
+                       err = fmt.Errorf("draft nonce is not unique (see draft %s)", oldid)
                } else {
-                       w.out, w.err = GetCert("certby/draft", w.docid)
+                       c, err = GetCert("certby/draft", w.docid)
                }
                return
        } else if _, ok := err.(store.NotFoundError); !ok {
-               w.err = err
                return
        }
 
        err = db.Begin(w.docid)
        if err != nil {
-               w.err = err
                return
        }
        err = db.Set("draft", w.docid, w.in)
        if err != nil {
-               w.err = err
                return
        }
        err = db.Set("draftby/key.issuer.nonce", nonce, []byte(w.docid))
        if err != nil {
-               w.err = err
                return
        }
        cert, err := newDebitCert(w)
@@ -359,7 +350,6 @@ func handleDraft(w *work) {
                return
        }
        certid := document.Id(signed)
-       w.out = c
        err = db.Set("cert", certid, c)
        if err != nil {
                // internal error
@@ -386,24 +376,20 @@ func handleDraft(w *work) {
        return
 }
 
-func handleDebit(w *work) {
-       c, err := GetCert("certby/debit", w.docid)
+func handleDebit(w *work) (c []byte, err error) {
+       c, err = GetCert("certby/debit", w.docid)
        if err == nil {
-               w.out = c
                return
        } else if _, ok := err.(store.NotFoundError); !ok {
                // internal error
-               w.err = err
                return
        }
        err = db.Begin(w.docid)
        if err != nil {
-               w.err = err
                return
        }
        err = db.Set("cert", w.docid, w.in)
        if err != nil {
-               w.err = err
                return
        }
        // TODO: check pubkey etc
@@ -443,6 +429,10 @@ func handleDebit(w *work) {
        return
 }
 
+func isIssuer(c *document.Cert) bool {
+       return c.Issuer == c.Holder
+}
+
 func newDebitCert(w *work) (*document.DebitCert, error) {
        cert := new(document.DebitCert)
        cert.Holder = w.draft.Drawer
@@ -475,6 +465,7 @@ func newDebitCert(w *work) (*document.DebitCert, error) {
                        // internal error
                        return nil, err
                }
+               // TODO: make sure oldcert and newcert cannot become inconsistent
                // TODO: this is a hack
                oldcert, err := document.ToCert(iv)
                if err != nil {
@@ -487,8 +478,8 @@ func newDebitCert(w *work) (*document.DebitCert, error) {
                if cert.Balance <= -IntLimit {
                        return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
                }
-               if oldcert.Balance > 0 && cert.Balance < 0 {
-                       return nil, fmt.Errorf("insufficient funds: %d", oldcert.Balance)
+               if !isIssuer(&cert.Cert) && cert.Balance < 0 {
+                       return nil, fmt.Errorf("insufficient funds: %d, draft: %d", oldcert.Balance, cert.Difference)
                }
                cert.LastDebitSerial = oldcert.LastDebitSerial
                cert.LastCreditSerial = oldcert.LastCreditSerial
@@ -516,9 +507,13 @@ func newCreditCert(w *work) (*document.CreditCert, error) {
        cert.Drawer = w.debit.Holder
        cert.DebitCert = w.docid
 
-       oid, err := db.Get("certby/key", w.debit.Beneficiary)
+       oid, err := db.Get("certby/key.issuer", w.account)
        oldcertid := string(oid)
        if err != nil {
+               if _, ok := err.(store.NotFoundError); !ok {
+                       // internal error
+                       return nil, err
+               }
                // this is the first cert
                cert.Serial = 1
                cert.Balance = cert.Difference
@@ -546,6 +541,10 @@ func newCreditCert(w *work) (*document.CreditCert, error) {
                if cert.Balance >= IntLimit {
                        return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
                }
+               // sanity check
+               if isIssuer(&cert.Cert) && cert.Balance > 0 {
+                       return nil, fmt.Errorf("internal error")
+               }
                cert.LastDebitSerial = oldcert.LastDebitSerial
                cert.LastCreditSerial = oldcert.LastCreditSerial
                if _, ok := iv.(*document.DebitCert); ok {
@@ -607,6 +606,8 @@ func Init(rootdir string, sk *openpgp.Entity) (err error) {
        if err != nil {
                return
        }
+       newchan = make(chan *work)
+       delchan = make(chan *work)
        go dispatch()
        return
 }