diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index 5fadda9..c6ccf0f 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -411,9 +411,19 @@ SELECT i.issue_number AS issue_number, i.is_open AS is_open, i.winner_login AS winner_login, + i.transaction_hash AS transaction_hash, + i.contract_address AS contract_address, + i.confirm_hash AS confirm_hash, + i.execute_hash AS execute_hash, + i.payout_hash AS payout_hash, + i.watch_hash AS watch_hash, + i.payout_receipt AS payout_receipt, i.commit_sha AS commit_sha, i.title AS title, i.comment_id AS comment_id, + i.balance_eth AS balance_eth, + i.tokens AS tokens, + i.value_usd AS value_usd, i.repo_id AS repo_id, r.owner AS owner, r.repo AS repo diff --git a/src/clj/commiteth/bounties.clj b/src/clj/commiteth/bounties.clj index d24700d..dd6727e 100644 --- a/src/clj/commiteth/bounties.clj +++ b/src/clj/commiteth/bounties.clj @@ -1,5 +1,6 @@ (ns commiteth.bounties (:require [commiteth.db.issues :as issues] + [commiteth.db.bounties :as db-bounties] [commiteth.db.users :as users] [commiteth.db.repositories :as repos] [commiteth.eth.core :as eth] @@ -21,7 +22,50 @@ (let [labels (:labels issue)] (some #(= label-name (:name %)) labels))) -(defn deploy-contract [owner-address issue-id] +(defn transition [{:keys [issue-id tx-info] :as bounty} state] + (let [bounty-not= (fn [current db] + (some #(not= (%1 current) (%1 db)) + (disj (set (keys current)) :tx-info))) + bounty-from-db (issues/get-issue-by-id issue-id) + bounty (and (bounty-not= bounty bounty-from-db) + (merge bounty-from-db bounty))] + (when bounty + (case state + :deploying + (tracker/track-tx! tx-info) + + :opened + (do + (tracker/untrack-tx! {:issue-id (:issue-id bounty) + :tx-hash (:transaction-hash bounty) + :result (:contract-address bounty) + :type :deploy}) + (github/update-bounty-comment-image bounty)) + + :pending-sob-confirmation + (tracker/track-tx! tx-info) + + :pending-maintainer-confirmation + (tracker/untrack-tx! tx-info) + + :paid-with-receipt + (db-bounties/update-payout-receipt issue-id (:payout-receipt bounty)) + + :watch-set + (tracker/track-tx! tx-info) + + :watch-reset + (tracker/untrack-tx! tx-info) + + :update-balances + (issues/update-balances (:contract-address bounty) + (:balance-eth bounty) + (:tokens bounty) + (:value-usd bounty)) + + ) + (github/update-comment bounty state)))) +(defn deploy-contract [owner-address issue-id] (if (empty? owner-address) (log/errorf "issue %s: Unable to deploy bounty contract because repo owner has no Ethereum addres" issue-id) (try @@ -30,8 +74,8 @@ :internal-tx-id [:deploy issue-id]})] (do (log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id (:tx-hash tx-info)) - (github/update-comment (into tx-info [:issue-id issue-id])) - (tracker/track-tx! tx-info)) + (transition {:issue-id issue-id + :tx-info tx-info} :deploying)) (log/errorf "issue %s Failed to deploy contract to %s" issue-id owner-address)) (catch Exception ex (log/errorf ex "issue %s: deploy-contract exception" issue-id))))) @@ -106,6 +150,7 @@ (if-let [merged-or-paid? (or (:winner_login bounty) (:payout_hash bounty))] (cond + (:payout_receipt bounty) :paid-with-receipt (:payout_hash bounty) :paid (nil? (:payout_address bounty)) :pending-contributor-address ;; `confirm_hash` is set by us as soon as a PR is merged and the @@ -121,6 +166,8 @@ (seq (:tokens bounty)) :funded (:contract_address bounty) :opened)))) + + (comment (def user 97496) diff --git a/src/clj/commiteth/eth/tracker.clj b/src/clj/commiteth/eth/tracker.clj index f46270e..7da67f6 100644 --- a/src/clj/commiteth/eth/tracker.clj +++ b/src/clj/commiteth/eth/tracker.clj @@ -62,8 +62,42 @@ (:tx-hash @current-tx) (:type @current-tx)) (reset! current-tx nil))) + ) + +(defrecord ParallelTxTracker [current-txs] + ITxTracker + (try-reserve-nonce [this] + (let [nonce (get-nonce) + monitored-nonces (set (keys current-txs)) + first-available-nonce (some #(if (monitored-nonces %1) nil %1) (iterate inc nonce))] + (swap! current-txs assoc nonce nil))) + + (drop-nonce [this nonce] + (swap! current-txs dissoc nonce)) + + (track-tx [this tx-info] + (swap! current-txs update (:nonce tx-info) merge tx-info)) + + (untrack-tx [this tx-info] + (when (contains? (set (keys current-txs)) (:nonce tx-info)) + (swap! current-txs dissoc (:nonce tx-info)))) + + (prune-txs [this unmined-txs] + (swap! current-txs + (fn [txs] + (let [unmined-tx-hashes (set (map :tx-hash unmined-txs)) + time-threshold (t/minus (t/now) (t/minutes 10)) + nonces-to-remove + (->> txs + vals + (filter #(or (unmined-tx-hashes (:tx-hash %1)) + (and (:timestamp %1) + (t/before? (:timestamp %1) time-threshold)))) + (map :nonce))] + (apply dissoc txs nonces-to-remove))))) ) + (def tx-tracker (SequentialTxTracker. (atom nil))) (defn try-reserve-nonce! [] diff --git a/src/clj/commiteth/github/core.clj b/src/clj/commiteth/github/core.clj index e756c54..1970896 100644 --- a/src/clj/commiteth/github/core.clj +++ b/src/clj/commiteth/github/core.clj @@ -234,7 +234,7 @@ (str "Contract address: [" addr "](" url-base "/address/" addr ")\n"))) (defn generate-open-comment - [owner repo issue-number contract-address eth-balance eth-balance-str tokens] + [owner repo issue-number contract-address eth-balance tokens] (let [image-url (md-image "QR Code" (get-qr-url owner repo issue-number eth-balance)) site-url (md-url (server-address) (server-address))] (format (str "Current balance: %s ETH\n" @@ -247,14 +247,14 @@ (if (on-testnet?) "To fund it, send test ETH or test ERC20/ERC223 tokens to the contract address." "To fund it, send ETH or ERC20/ERC223 tokens to the contract address.")) - eth-balance-str image-url site-url))) + eth-balance image-url site-url))) (defn learn-more-text [] (let [site-url (md-url (server-address) (server-address))] (format "Visit %s to learn more.\n" site-url))) (defn generate-merged-comment - [contract-address eth-balance-str tokens winner-login winner-address-missing?] + [contract-address eth-balance tokens winner-login winner-address-missing?] (format (str "Balance: %s ETH\n" (token-balances-text tokens) (contract-addr-text contract-address) @@ -264,7 +264,7 @@ "Pending maintainer confirmation") "\n") "Winner: %s\n" (learn-more-text)) - eth-balance-str winner-login)) + eth-balance winner-login)) (defn generate-paid-comment [contract-address eth-balance-str tokens payee-login] @@ -294,16 +294,16 @@ :otp))] (assoc req :body (json/generate-string (or raw-query proper-query))))) -(defn update-bounty-comment-image [issue-id owner repo issue-number contract-address eth-balance eth-balance-str tokens] - (let [hash (github-comment-hash owner repo issue-number eth-balance) +(defn update-bounty-comment-image [{:keys [issue-id owner repo issue-number contract-address balance-eth tokens]}] + (let [hash (github-comment-hash owner repo issue-number balance-eth) issue-url (str owner "/" repo "/issues/" (str issue-number)) png-data (png-rendering/gen-comment-image contract-address - eth-balance-str + balance-eth tokens issue-url)] (log/debug "update-bounty-comment-image" issue-id owner repo issue-number) - (log/debug contract-address eth-balance-str) + (log/debug contract-address balance-eth) (log/debug "hash" hash) (if png-data @@ -331,53 +331,43 @@ (defn update-comment "Update comment for an open bounty issue" [{:keys [issue-id owner repo comment-id issue-number contract-address - eth-balance eth-balance-str tokens + balance-eth tokens payout-receipt - winner-login winner-address transaction-hash] :as issue}] - (let [state (cond (nil? comment-id) :deployed - (not (nil? payout-receipt)) :paid - (not (str/blank? winner-login)) :merged - :else :open) - comment (cond (= state :deployed) - (generate-deploying-comment owner repo issue-number transaction-hash) - (= state :open) - (generate-open-comment owner - repo - issue-number - contract-address - eth-balance - eth-balance-str - tokens) - (= state :merged) - (generate-merged-comment contract-address - eth-balance-str - tokens - winner-login - (str/blank? winner-address)) - (= state :paid) - (generate-paid-comment contract-address - eth-balance-str - tokens - winner-login) - )] - (when (and (= state :merged) (empty? winner-address)) - (log/warn "issue %s: Cannot sign pending bounty - winner has no payout address" issue-id)) - (when (= :paid state) - (db-bounties/update-payout-receipt issue-id payout-receipt)) - - (when (= :open state) - (update-bounty-comment-image issue-id owner repo issue-number contract-address eth-balance eth-balance-str tokens)) - + winner-login winner-address transaction-hash] :as issue} + state] + (let [comment (case state + :deploying + (generate-deploying-comment owner repo issue-number transaction-hash) + :opened + (generate-open-comment owner + repo + issue-number + contract-address + balance-eth + tokens) + (:pending-maintainer-confirmation :pending-contributor-address) + (generate-merged-comment contract-address + balance-eth + tokens + winner-login + (str/blank? winner-address)) + :paid-with-receipt + (generate-paid-comment contract-address + balance-eth + tokens + winner-login) + :else nil)] (log/debug (str "Updating " owner "/" repo "/" issue-number " comment #" comment-id " with contents: " comment)) - (if (= state :deployed) + (if (= state :deploying) (let [resp (issues/create-comment owner repo issue-number comment (self-auth-params)) comment-id (:id resp)] (db-issues/update-comment-id issue-id comment-id)) - (let [req (make-patch-request "repos/%s/%s/issues/comments/%s" - [owner repo comment-id] - (assoc (self-auth-params) :body comment))] - (tentacles/safe-parse (http/request req)))))) + (when comment + (let [req (make-patch-request "repos/%s/%s/issues/comments/%s" + [owner repo comment-id] + (assoc (self-auth-params) :body comment))] + (tentacles/safe-parse (http/request req))))))) (defn get-issue [owner repo issue-number] diff --git a/src/clj/commiteth/scheduler.clj b/src/clj/commiteth/scheduler.clj index da46bae..42f355b 100644 --- a/src/clj/commiteth/scheduler.clj +++ b/src/clj/commiteth/scheduler.clj @@ -2,9 +2,9 @@ (:require [commiteth.eth.core :as eth] [commiteth.eth.multisig-wallet :as multisig] [commiteth.eth.token-data :as token-data] - [commiteth.eth.tracker :as tracker] [commiteth.github.core :as github] [commiteth.db.issues :as issues] + [commiteth.eth.tracker :as tracker] [commiteth.util.util :refer [to-map]] [taoensso.tufte :as tufte :refer (defnp p profiled profile)] [commiteth.db.bounties :as db-bounties] @@ -46,34 +46,17 @@ [] (log/info "In update-issue-contract-address") (p :update-issue-contract-address - (doseq [{:keys [issue-id transaction-hash]} (issues/list-pending-deployments)] - (log/infof "issue %s: pending deployment: %s" issue-id transaction-hash) - (try - (when-let [receipt (eth/get-transaction-receipt transaction-hash)] - (log/infof "issue %s: update-issue-contract-address: tx receipt: %s" issue-id receipt) - (if-let [contract-address (multisig/find-created-multisig-address receipt)] - (let [_ (tracker/untrack-tx! {:issue-id issue-id - :tx-hash transaction-hash - :result contract-address - :type :deploy}) - {:keys [owner repo comment-id issue-number] :as issue} - (issues/get-issue-by-id issue-id) - balance-eth-str (eth/get-balance-eth contract-address 6) - balance-eth (read-string balance-eth-str) - tokens {}] - (log/infof "issue %s: Updating comment" issue-id) - (github/update-comment (to-map issue-id - owner - repo - comment-id - issue-number - contract-address - balance-eth - balance-eth-str - tokens))) - (log/errorf "issue %s: Failed to find contract address in tx logs" issue-id))) - (catch Throwable ex - (log/errorf ex "issue %s: update-issue-contract-address exception:" issue-id))))) + (doseq [{:keys [issue-id transaction-hash] :as issue} (issues/list-pending-deployments)] + (log/infof "issue %s: pending deployment: %s" issue-id transaction-hash) + (try + (when-let [receipt (eth/get-transaction-receipt transaction-hash)] + (log/infof "issue %s: update-issue-contract-address: tx receipt: %s" issue-id receipt) + (if-let [contract-address (multisig/find-created-multisig-address receipt)] + (bounties/transition (assoc issue :contract-address contract-address) + :opened) + (log/errorf "issue %s: Failed to find contract address in tx logs" issue-id))) + (catch Throwable ex + (log/errorf ex "issue %s: update-issue-contract-address exception:" issue-id))))) (log/info "Exit update-issue-contract-address")) @@ -102,15 +85,18 @@ ;; TODO(martin) delete this shortly after org-dashboard deploy ;; as we're now setting `winner_login` when handling a new claims ;; coming in via webhooks (see `commiteth.routes.webhooks/handle-claim`) - (db-bounties/update-winner-login issue-id winner-login) - (let [value (eth/get-balance-hex contract-address)] - (when-not (empty? winner-address) - (let [tx-info (multisig/send-all {:contract contract-address - :payout-address winner-address - :internal-tx-id [:execute issue-id]})] - (log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address winner-address (:tx-hash tx-info)) - (tracker/track-tx! tx-info) - (github/update-comment issue)))) + ;(db-bounties/update-winner-login issue-id winner-login) + (if (empty? winner-address) + (do + (log/warn "issue %s: Cannot sign pending bounty - winner has no payout address" issue-id) + (bounties/transition {:issue-id issue-id} :pending-contributor-address)) + (let [tx-info (multisig/send-all {:contract contract-address + :payout-address winner-address + :internal-tx-id [:execute issue-id]})] + (log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address winner-address (:tx-hash tx-info)) + (bounties/transition {:execute-hash (:tx-hash tx-info) + :issue-id issue-id + :tx-info tx-info} :pending-sob-confirmation))) (catch Throwable ex (log/error ex "issue %s: self-sign-bounty exception" issue-id))))) (log/info "Exit self-sign-bounty")) @@ -127,10 +113,14 @@ (log/infof "issue %s: execution receipt for issue " issue-id receipt) (when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)] (log/infof "issue %s: confirm hash:" issue-id confirm-hash) - (tracker/untrack-tx! {:issue-id issue-id - :tx-hash execute-hash - :result confirm-hash - :type :execute}))) + (bounties/transition {:issue-id issue-id + :tx-info {:issue-id issue-id + :tx-hash execute-hash + :result confirm-hash + :type :execute}} + :pending-maintainer-confirmation) + + )) (catch Throwable ex (log/errorf ex "issue %s: update-confirm-hash exception:" issue-id))) ) (log/info "Exit update-confirm-hash"))) @@ -144,10 +134,12 @@ (log/infof "issue %s: pending watch call %s" issue-id watch-hash) (try (when-let [receipt (eth/get-transaction-receipt watch-hash)] - (tracker/untrack-tx! {:issue-id issue-id - :tx-hash watch-hash - :result nil - :type :watch})) + (bounties/transition {:issue-id issue-id + :tx-info + {:issue-id issue-id + :tx-hash watch-hash + :result nil + :type :watch}} :watch-reset)) (catch Throwable ex (log/errorf ex "issue %s: update-watch-hash exception:" issue-id)) )))) @@ -182,10 +174,9 @@ (log/infof "issue %s: Detected bounty with funds and confirmed payout, calling executeTransaction" issue-id) (let [execute-tx-hash (multisig/execute-tx contract-address confirm-hash)] (log/infof "issue %s: execute tx: %s" issue-id execute-tx-hash)))) - (do (log/infof "issue %s: Payout has succeeded, payout receipt %s" issue-id receipt) - (github/update-comment (assoc issue :payout-receipt receipt))))) + (bounties/transition (assoc issue :payout-receipt receipt) :paid-with-receipt)))) (when (older-than-3h? updated) (log/warn "issue %s: Resetting payout hash for issue as it has not been mined in 3h" issue-id) (db-bounties/reset-payout-hash issue-id))) @@ -222,7 +213,9 @@ (let [tx-info (multisig/watch-token {:bounty-addr bounty-addr :token tla :internal-tx-id [:watch issue-id]})] - (tracker/track-tx! tx-info))))))) + (bounties/transition {:issue-id issue-id + :tx-info tx-info} + :watch-set))))))) (catch Throwable ex (log/error ex "bounty %s: update-bounty-token-balances exception" bounty-addr)))) (log/info "Exit update-bounty-token-balances")) @@ -278,12 +271,14 @@ (log/info "tokens (db):" tokens (type tokens) (type (:SNT tokens))) (log/info "tokens (chain):" token-balances (type token-balances) (type (:SNT token-balances))) (log/debug "tokens cmp:" (= tokens token-balances)) + (bounties/transition {:issue-id issue-id + :balance-eth current-balance-eth + :tokens token-balances + :value-usd (fiat-util/bounty-usd-value + (merge token-balances {:ETH current-balance-eth}))} :update-balances) - (issues/update-balances contract-address - current-balance-eth token-balances - (fiat-util/bounty-usd-value - (merge token-balances {:ETH current-balance-eth}))) - (github/update-comment issue)))) + + ))) (catch Throwable ex (log/error ex "issue %s: update-balances exception" issue-id))))) (log/info "Exit update-balances"))