fix balance check in server logic
[epoint] / pkg / server / server.go
index 71bd3fc..8be10b2 100644 (file)
@@ -9,22 +9,75 @@ import (
        "epoint/key"
        "epoint/store"
        "fmt"
+       "log"
        "time"
 )
 
 // TODO: do in docs?
 const IntLimit = 1e15
 
+// transaction: either draft or debit cert
+type work struct {
+       next    *work
+       docid   string
+       account string
+       in      []byte
+       out     []byte
+       err     error
+       signed  *document.Signed
+       draft   *document.Draft
+       debit   *document.DebitCert
+       sync    chan int
+}
+
 var db *store.Conn
+var serverkey *openpgp.Entity
+var newchan chan *work
+var delchan chan *work
+
+type worklist struct {
+       head *work
+       tail *work
+}
+
+func pushwork(ws *worklist, w *work) {
+       if ws.tail == nil {
+               ws.head = w
+               ws.tail = w
+       } else {
+               ws.tail.next = w
+               ws.tail = w
+       }
+}
 
-func StoreSk(sk *openpgp.Entity) (err error) {
-       // TODO: initkey should save serverkey in db
-       b := new(bytes.Buffer)
-       err = sk.Serialize(b)
+func popwork(ws *worklist) *work {
+       w := ws.head
+       if w == nil {
+               return nil
+       }
+       ws.head = w.next
+       if ws.head == nil {
+               ws.tail = nil
+       }
+       return w
+}
+
+func setserverkey(e *openpgp.Entity) (err error) {
+       serverkey = e
+       err = key.SelfSign(e)
        if err != nil {
                return
        }
-       return db.Set("key", fmt.Sprintf("%X", sk.PrimaryKey.Fingerprint), b.Bytes())
+       d, err := key.Format(e)
+       if err != nil {
+               return
+       }
+       err = db.Set("key", key.Id(e), d)
+       if err != nil {
+               return
+       }
+       err = db.Set("", "serverkey", d)
+       return
 }
 
 func GetKeys(fpr string) (es openpgp.EntityList, err error) {
@@ -52,22 +105,27 @@ func AddKeys(d []byte) (err error) {
        }
        for _, e := range entities {
                // TODO: various checks..
+               // TODO: collect errors instead of aborting addkeys
                isIssuer, issuer, denom, err1 := key.Check(e)
                err = err1
                if err != nil {
-                       // TODO..
-                       continue
+                       return
                }
                if !isIssuer {
-                       es, err := GetKeys(issuer)
+                       es, err1 := GetKeys(issuer)
+                       err = err1
                        if err != nil {
-                               // TODO..
-                               continue
+                               return
                        }
-                       ok, _, den, err := key.Check(es[0])
-                       if !ok || err != nil || den != denom {
-                               // TODO..
-                               continue
+                       ok, _, den, err1 := key.Check(es[0])
+                       err = err1
+                       if err != nil {
+                               // internal error
+                               return
+                       }
+                       if !ok || den != denom {
+                               err = fmt.Errorf("Issuer key check failed")
+                               return
                        }
                }
                b := new(bytes.Buffer)
@@ -75,8 +133,8 @@ func AddKeys(d []byte) (err error) {
                if err != nil {
                        return
                }
-               fpr := fmt.Sprintf("%X", e.PrimaryKey.Fingerprint)
-               err = db.Set("key", fpr, b.Bytes())
+               fpr := key.Id(e)
+               err = db.Insert("key", fpr, b.Bytes())
                if err != nil {
                        return
                }
@@ -92,141 +150,306 @@ func AddKeys(d []byte) (err error) {
        return
 }
 
-func CertByDraft(draftid string) (d []byte, err error) {
-       certid, err := db.Get("certby/draft", draftid)
+// Get cert through the named store
+func GetCert(name, id string) (d []byte, err error) {
+       certid, err := db.Get(name, id)
        if err != nil {
-               // TODO: we have the draft but the cert is not ready
+               // ok if notfound
                return
        }
        d, err = db.Get("cert", string(certid))
        if err != nil {
-               // shouldn't happen, cert is not available
+               // internal error: cert is not available
                return
        }
        return
 }
 
-func CertByDebitCert(debitid string) (d []byte, err error) {
-       creditid, err := db.Get("certby/debit", debitid)
+func EvalDraft(d []byte) (c []byte, err error) {
+       iv, signed, err := document.Parse(d)
        if err != nil {
-               // TODO: we have the debit cert but the credit cert is not ready
                return
        }
-       d, err = db.Get("cert", string(creditid))
+       draft, ok := iv.(*document.Draft)
+       if !ok {
+               err = fmt.Errorf("EvalDraft: expected a draft document")
+               return
+       }
+       k, err := db.Get("key", draft.Drawer)
        if err != nil {
-               // shouldn't happen, cert is not available
                return
        }
-       return
+       // TODO: key.Parse
+       kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       if err != nil {
+               // internal error: pubkey cannot be parsed
+               return
+       }
+       err = document.Verify(signed, kr)
+       if err != nil {
+               return
+       }
+       //      _, issuer, denom, err := key.Check(kr[0])
+       //      if err != nil {
+       //              return
+       //      }
+       // TODO: do various format checks (AuthorizedBy check etc)
+       if draft.Amount <= 0 || draft.Amount >= IntLimit {
+               err = fmt.Errorf("draft amount is invalid: %d", draft.Amount)
+               return
+       }
+       k, err = db.Get("key", draft.Beneficiary)
+       if err != nil {
+               return
+       }
+       kr, err = openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       if err != nil {
+               // internal error: pubkey cannot be parsed
+               return
+       }
+       return addDraft(d, signed, draft)
 }
 
-// parse clear signed draft and verify it
-func ParseDraft(d []byte) (draft *document.Draft, draftid string, err error) {
+func EvalDebitCert(d []byte) (c []byte, err error) {
        iv, signed, err := document.Parse(d)
        if err != nil {
                return
        }
-       draft, ok := iv.(*document.Draft)
+       cert, ok := iv.(*document.DebitCert)
        if !ok {
-               err = fmt.Errorf("ParseDraft: expected a draft docuent")
+               err = fmt.Errorf("ParseDebitCert: expected a debit docuent")
                return
        }
-       draftid = document.Id(signed)
 
-       k, err := db.Get("key", draft.Drawer)
+       kr := openpgp.EntityList{serverkey}
+       if key.Id(serverkey) != cert.AuthorizedBy {
+               // TODO: ...
+               k := []byte(nil)
+               k, err = db.Get("key", cert.AuthorizedBy)
+               if err != nil {
+                       return
+               }
+               kr, err = openpgp.ReadKeyRing(bytes.NewBuffer(k))
+               if err != nil {
+                       // internal error: pubkey cannot be parsed
+                       return
+               }
+       }
+       // must clean up to make sure the hash is ok
+       err = document.Verify(signed, kr)
        if err != nil {
                return
        }
-       kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       return addDebit(d, signed, cert)
+}
+
+func addDraft(d []byte, signed *document.Signed, draft *document.Draft) (c []byte, err error) {
+       w := new(work)
+       w.docid = document.Id(signed)
+       w.account = fmt.Sprintf("%s.%s", draft.Drawer, draft.Issuer)
+       w.in = d
+       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
+}
+
+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.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
+                               works[w.account] = new(worklist)
+                               go handle(w)
+                       } else {
+                               pushwork(ws, w)
+                       }
+               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(wnext)
+                       }
+               }
+       }
+}
+
+func handle(w *work) {
+       log.Printf("start work: %s", w.account)
+       if w.debit != nil {
+               w.out, w.err = handleDebit(w)
+       } else if w.draft != nil {
+               w.out, w.err = handleDraft(w)
+       } else {
+               panic("unreachable")
+       }
+       log.Printf("finish work: %s outlen: %d", w.account, len(w.out))
+       delchan <- w
+       w.sync <- 0
+}
+
+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 {
+                       err = fmt.Errorf("draft nonce is not unique (see draft %s)", oldid)
+               } else {
+                       c, err = GetCert("certby/draft", w.docid)
+               }
+               return
+       } else if _, ok := err.(store.NotFoundError); !ok {
+               return
+       }
+
+       err = db.Begin(w.docid)
        if err != nil {
-               // internal error: pubkey cannot be parsed
                return
        }
-       err = document.Verify(signed, kr)
+       err = db.Set("draft", w.docid, w.in)
        if err != nil {
                return
        }
-       _, issuer, denom, err := key.Check(kr[0])
+       err = db.Set("draftby/key.issuer.nonce", nonce, []byte(w.docid))
        if err != nil {
                return
        }
-       k, err = db.Get("key", draft.Beneficiary)
+       cert, err := newDebitCert(w)
        if err != nil {
+               // probably client error
+               db.End(w.docid)
                return
        }
-       kr, err = openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       c, signed, err := document.Format(cert, serverkey)
        if err != nil {
-               // internal error: pubkey cannot be parsed
                return
        }
-       _, issuer2, denom2, err := key.Check(kr[0])
+       certid := document.Id(signed)
+       err = db.Set("cert", certid, c)
        if err != nil {
+               // internal error
                return
        }
-       if draft.Issuer != issuer ||
-               draft.Issuer != issuer2 ||
-               draft.Denomination != denom ||
-               draft.Denomination != denom2 {
-               err = fmt.Errorf("Issuer or denomination mismatch")
+       err = db.Set("certby/draft", w.docid, []byte(certid))
+       if err != nil {
+               // internal error
                return
        }
-
-       // TODO: do various format checks (AuthorizedBy check etc)
-       if draft.Amount <= 0 || draft.Amount >= IntLimit {
-               err = fmt.Errorf("draft amount is invalid: %d", draft.Amount)
+       // TODO: append?
+       err = db.Set("certby/key.issuer.serial", fmt.Sprintf("%s.%09d", w.account, cert.Serial), []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certby/key.issuer", w.account, []byte(certid))
+       if err != nil {
+               // internal error
                return
        }
+       // TODO: run journal cleanup in case of client errors
+       err = db.End(w.docid)
        return
 }
 
-func ParseDebitCert(d []byte) (cert *document.DebitCert, certid string, err error) {
-       iv, signed, err := document.Parse(d)
+func handleDebit(w *work) (c []byte, err error) {
+       c, err = GetCert("certby/debit", w.docid)
+       if err == nil {
+               return
+       } else if _, ok := err.(store.NotFoundError); !ok {
+               // internal error
+               return
+       }
+       err = db.Begin(w.docid)
        if err != nil {
                return
        }
-       cert, ok := iv.(*document.DebitCert)
-       if !ok {
-               err = fmt.Errorf("ParseDebitCert: expected a debit docuent")
+       err = db.Set("cert", w.docid, w.in)
+       if err != nil {
                return
        }
-
-       k, err := db.Get("key", cert.AuthorizedBy)
+       // TODO: check pubkey etc
+       cert, err := newCreditCert(w)
        if err != nil {
+               // internal error
                return
        }
-       // TODO: keep our key at hand
-       kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       c, signed, err := document.Format(cert, serverkey)
        if err != nil {
-               // internal error: pubkey cannot be parsed
+               // internal error
                return
        }
-       // must clean up to make sure the hash is ok
-       err = document.Verify(signed, kr)
+       certid := document.Id(signed)
+       err = db.Set("cert", certid, c)
        if err != nil {
+               // internal error
                return
        }
-
-       certid = document.Id(signed)
+       err = db.Set("certby/debit", w.docid, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       // TODO: append?
+       err = db.Set("certby/key.issuer.serial", fmt.Sprintf("%s.%09d", w.account, cert.Serial), []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certby/key.issuer", w.account, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       db.End(w.docid)
        return
 }
 
-func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, error) {
+func isIssuer(c *document.Cert) bool {
+       return c.Issuer == c.Holder
+}
+
+func newDebitCert(w *work) (*document.DebitCert, error) {
        cert := new(document.DebitCert)
-       cert.Holder = draft.Drawer
-       cert.Date = time.Seconds()
+       cert.Holder = w.draft.Drawer
+       cert.Date = time.Now().Unix()
        cert.Denomination = "epoint"
-       cert.Issuer = draft.Issuer
-       cert.AuthorizedBy = draft.AuthorizedBy
-       cert.Difference = -draft.Amount
-       cert.Draft = draftid
-       cert.Beneficiary = draft.Beneficiary
+       cert.Issuer = w.draft.Issuer
+       cert.AuthorizedBy = w.draft.AuthorizedBy
+       cert.Difference = -w.draft.Amount
+       cert.Draft = w.docid
+       cert.Beneficiary = w.draft.Beneficiary
 
-       oid, err := db.Get("certby/key", draft.Drawer)
+       oid, err := db.Get("certby/key.issuer", w.account)
        oldcertid := string(oid)
        if err != nil {
                // first cert: drawer is issuer
-               if draft.Drawer != draft.Issuer {
-                       return nil, fmt.Errorf("drawer must be the issuer when drawing an empty account")
+               if w.draft.Drawer != w.draft.Issuer {
+                       return nil, fmt.Errorf("drawer must be the issuer when drawing an empty account (%s != %s)", w.draft.Drawer, w.draft.Issuer)
                }
                cert.Serial = 1
                cert.Balance = cert.Difference
@@ -242,6 +465,7 @@ func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, e
                        // 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 {
@@ -254,12 +478,12 @@ func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, e
                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
-               if _,ok := iv.(*document.DebitCert); ok {
+               if _, ok := iv.(*document.DebitCert); ok {
                        cert.LastDebitSerial = oldcert.Serial
                } else {
                        cert.LastCreditSerial = oldcert.Serial
@@ -269,23 +493,27 @@ func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, e
        return cert, nil
 }
 
-func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert *document.DebitCert) (*document.CreditCert, error) {
+func newCreditCert(w *work) (*document.CreditCert, error) {
        cert := new(document.CreditCert)
        // TODO: get from old cert instead?
-       cert.Holder = dcert.Beneficiary
-       cert.Date = time.Seconds()
+       cert.Holder = w.debit.Beneficiary
+       cert.Date = time.Now().Unix()
        // TODO: get these from the cert holder pubkey
        cert.Denomination = "epoint"
-       cert.Issuer = draft.Issuer
-       cert.AuthorizedBy = dcert.AuthorizedBy // TODO: draft vs dcert vs serverside decision
-       cert.Difference = -dcert.Difference
-       cert.Draft = draftid
-       cert.Drawer = dcert.Holder
-       cert.DebitCert = dcertid
+       cert.Issuer = w.debit.Issuer
+       cert.AuthorizedBy = w.debit.AuthorizedBy // TODO: draft vs dcert vs serverside decision
+       cert.Difference = -w.debit.Difference
+       cert.Draft = w.debit.Draft
+       cert.Drawer = w.debit.Holder
+       cert.DebitCert = w.docid
 
-       oid, err := db.Get("certby/key", dcert.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
@@ -313,9 +541,13 @@ func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert
                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 {
+               if _, ok := iv.(*document.DebitCert); ok {
                        cert.LastDebitSerial = oldcert.Serial
                } else {
                        cert.LastCreditSerial = oldcert.Serial
@@ -325,134 +557,7 @@ func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert
        return cert, nil
 }
 
-func EvalDraft(d []byte, sk *openpgp.Entity) (r []byte, err error) {
-       draft, draftid, err := ParseDraft(d)
-       if err != nil {
-               return
-       }
-       _, err = db.Get("draft", draftid)
-       if err == nil {
-               // found
-               // TODO: certby/draft might not be ready even if draft is there
-               return CertByDraft(draftid)
-       }
-       // if draft is ok we save it
-       err = db.Set("draft", draftid, d)
-       if err != nil {
-               // internal error
-               return
-       }
-       // TODO: db.Insert: fails if key exists
-       s := fmt.Sprintf("%s.%s", draft.Drawer, draft.Nonce)
-       _, err = db.Get("draftby/key.nonce", s)
-       if err == nil {
-               err = fmt.Errorf("draft nonce is not unique")
-               return
-       }
-       err = db.Set("draftby/key.nonce", s, d)
-       if err != nil {
-               // internal error
-               return
-       }
-
-       // debit cert
-       cert, err := NewDebitCert(draftid, draft)
-       if err != nil {
-               return
-       }
-       r, signed, err := document.Format(cert, sk)
-       certid := document.Id(signed)
-       err = db.Set("cert", certid, r)
-       if err != nil {
-               // internal error
-               return
-       }
-       err = db.Set("certby/draft", draftid, []byte(certid))
-       if err != nil {
-               // internal error
-               return
-       }
-       err = db.Set("certby/key", cert.Holder, []byte(certid))
-       if err != nil {
-               // internal error
-               return
-       }
-       // TODO: append?
-       err = db.Set("certby/key.serial", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
-       if err != nil {
-               // internal error
-               return
-       }
-       return
-}
-
-func EvalDebitCert(d []byte, sk *openpgp.Entity) (r []byte, err error) {
-       dcert, dcertid, err := ParseDebitCert(d)
-       if err != nil {
-               return
-       }
-       r, err = CertByDebitCert(dcertid)
-       if err == nil {
-               // found
-               return
-       }
-       // TODO: we only need the draft to know the issuer (+beneficiary)
-       // it should be in the pubkey
-       d, err = db.Get("draft", dcert.Draft)
-       if err != nil {
-               // internal error
-               return
-       }
-       iv, _, err := document.Parse(d)
-       if err != nil {
-               // internal error
-               return
-       }
-       draft, ok := iv.(*document.Draft)
-       if !ok {
-               // internal error
-               err = fmt.Errorf("EvalDebitCert: expected draft from internal db")
-               return
-       }
-
-       // credit side
-       // TODO: check pubkey etc
-       cert, err := NewCreditCert(dcert.Draft, draft, dcertid, dcert)
-       if err != nil {
-               // internal error
-               return
-       }
-       r, signed, err := document.Format(cert, sk)
-       if err != nil {
-               // internal error
-               return
-       }
-       certid := document.Id(signed)
-       err = db.Set("cert", certid, r)
-       if err != nil {
-               // internal error
-               return
-       }
-       err = db.Set("certby/debit", dcertid, []byte(certid))
-       if err != nil {
-               // internal error
-               return
-       }
-       err = db.Set("certby/key", cert.Holder, []byte(certid))
-       if err != nil {
-               // internal error
-               return
-       }
-       // TODO: append?
-       err = db.Set("certby/key.serial", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
-       if err != nil {
-               // internal error
-               return
-       }
-       return
-}
-
-func Init(rootdir string) (err error) {
+func Init(rootdir string, sk *openpgp.Entity) (err error) {
        db, err = store.Open(rootdir)
        if err != nil {
                return
@@ -477,15 +582,15 @@ func Init(rootdir string) (err error) {
        if err != nil {
                return
        }
-       err = db.Ensure("certby/key")
+       err = db.Ensure("certby/key.issuer")
        if err != nil {
                return
        }
-       err = db.Ensure("certby/key.serial")
+       err = db.Ensure("certby/key.issuer.serial")
        if err != nil {
                return
        }
-       err = db.Ensure("draftby/key.nonce")
+       err = db.Ensure("draftby/key.issuer.nonce")
        if err != nil {
                return
        }
@@ -497,5 +602,12 @@ func Init(rootdir string) (err error) {
        if err != nil {
                return
        }
+       err = setserverkey(sk)
+       if err != nil {
+               return
+       }
+       newchan = make(chan *work)
+       delchan = make(chan *work)
+       go dispatch()
        return
 }