Re-execute watch if max attempt count exceeded;

block multiple execute tx attempts in case when there are funds still on
account (probably due to missing watch invocation);
invoke watch synchronously prior to confirming transactions
This commit is contained in:
Vitaliy Vlasov 2018-04-11 17:41:58 +03:00
parent c1158a187f
commit 90dc8a7534
No known key found for this signature in database
GPG Key ID: A7D57C347F2B2964
6 changed files with 115 additions and 72 deletions

View File

@ -348,6 +348,7 @@ WHERE issue_id = :issue_id;
-- :doc issues with a pending watch transaction
SELECT
issue_id,
contract_address,
watch_hash
FROM issues
WHERE watch_hash IS NOT NULL;

View File

@ -3,7 +3,9 @@
[commiteth.db.users :as users]
[commiteth.db.repositories :as repos]
[commiteth.db.comment-images :as comment-images]
[commiteth.db.bounties :as db-bounties]
[commiteth.eth.core :as eth]
[commiteth.eth.token-data :as token-data]
[commiteth.github.core :as github]
[commiteth.eth.multisig-wallet :as multisig]
[commiteth.util.png-rendering :as png-rendering]
@ -40,6 +42,26 @@
(issues/update-transaction-hash issue-id transaction-hash))
(log/error "Failed to deploy contract to" owner-address)))))
(defn watch-tokens [issue-id contract-address watch-hash]
"Helper function for updating internal ERC20 token balances to token multisig contract.
Will be called periodically for all open bounty contracts, or on-demand via /api/watchTokens endpoint."
(try
(doseq [[tla token-data] (token-data/as-map)]
(let [balance (multisig/token-balance contract-address tla)]
(log/info "balance for " tla ":" balance)
(when (> balance 0)
(do
(log/info "bounty at" contract-address "has" balance "of token" tla)
(let [internal-balance (multisig/token-balance-in-bounty contract-address tla)]
(when (and (nil? watch-hash)
(not= balance internal-balance))
(log/info "balances not in sync, calling watch")
(let [watch-hash (multisig/watch-token contract-address tla)]
(db-bounties/update-watch-hash issue-id watch-hash))))))))
(catch Throwable ex
(log/error "watch-tokens exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))
(defn add-bounty-for-issue [repo repo-id issue]
(let [{issue-id :id
issue-number :number

View File

@ -171,6 +171,8 @@
(do
(log/debug "/usage-metrics" user)
(ok (usage-metrics/usage-metrics-by-day))))
(POST "/watchTokens" {:keys [params]}
(ok (bounties/watch-tokens (:issue_id params) (:contract_address params))))
(context "/user" []

View File

@ -38,6 +38,8 @@
(update-balances)))
)
(defn update-issue-contract-address
"For each pending deployment: gets transaction receipt, updates db
state (contract-address, comment-id) and posts github comment"
@ -157,15 +159,25 @@
(db-bounties/update-confirm-hash issue-id confirm-hash)))))
(log/info "Exit update-confirm-hash"))
;; These define a (contract_addr -> attempt_cnt) map
;; used for tracking how many times we have checked for the particular
;; watch tx to be mined. If it hasn't been mined after a predefined number of checks,
;; reset the counter and schedule it for re-execution in update-contract-internal-balances
(def watch-attempts (atom {}))
(def max-watch-attempts 5)
(defn update-watch-hash
"Sets watch-hash to NULL for bounties where watch tx has been mined. Used to avoid unneeded watch transactions in update-bounty-token-balances"
[]
(p :update-watch-hash
(doseq [{issue-id :issue_id
contract-address :contract_address
watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
(log/info "pending watch call" watch-hash)
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
(swap! watch-attempts update contract-address #(inc (or %1 0)))
(when (or (< max-watch-attempts (get @watch-attempts contract-address 0))
(eth/get-transaction-receipt watch-hash))
(swap! watch-attempts dissoc contract-address)
(db-bounties/update-watch-hash issue-id nil)))))
@ -177,6 +189,13 @@
(println "hour diff:" diff)
(> diff 3)))
;; These define a (contract_addr -> attempt_cnt) map
;; used for tracking how many times we attempted to withdraw funds from
;; a particular account. All attempts above a predefined threshold are blocked,
;; as this may ensue in excessive bot fund drainage
(def execute-attempts (atom {}))
(def max-execute-attempts 3)
(defn update-payout-receipt
"Gets transaction receipt for each confirmed payout and updates payout_hash"
[]
@ -204,13 +223,17 @@
(> contract-eth-balance 0))
(do
(log/info "Contract still has funds")
(if (< (get @execute-attempts contract-address 0) max-execute-attempts)
(when (multisig/is-confirmed? contract-address confirm-id)
(log/info "Detected bounty with funds and confirmed payout, calling executeTransaction")
(let [execute-tx-hash (multisig/execute-tx contract-address confirm-id)]
(log/info "execute tx:" execute-tx-hash))))
(swap! execute-attempts update contract-address #(inc (or %1 0)))
(log/info "execute tx:" execute-tx-hash)))
(log/error "Max execute attempt count for " contract-address ", blocked")))
(do
(log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt)
(swap! execute-attempts dissoc contract-address)
(db-bounties/update-payout-receipt issue-id receipt)
(github/update-paid-issue-comment owner
repo
@ -238,28 +261,6 @@
:else n))
(defn update-bounty-token-balances
"Helper function for updating internal ERC20 token balances to token multisig contract. Will be called periodically for all open bounty contracts."
[issue-id bounty-addr watch-hash]
#_(log/info "In update-bounty-token-balances for issue" issue-id)
(doseq [[tla token-data] (token-data/as-map)]
(try
(let [balance (multisig/token-balance bounty-addr tla)]
(when (> balance 0)
(do
(log/info "bounty at" bounty-addr "has" balance "of token" tla)
(let [internal-balance (multisig/token-balance-in-bounty bounty-addr tla)]
(when (and (nil? watch-hash)
(not= balance internal-balance))
(log/info "balances not in sync, calling watch")
(let [hash (multisig/watch-token bounty-addr tla)]
(db-bounties/update-watch-hash issue-id hash)))))))
(catch Throwable ex
(do (log/error "update-bounty-token-balances exception:" ex)
(clojure.stacktrace/print-stack-trace ex)))))
#_(log/info "Exit update-bounty-token-balances"))
(defn update-contract-internal-balances
"It is required in our current smart contract to manually update it's internal balance when some tokens have been added."
[]
@ -269,7 +270,7 @@
bounty-address :contract_address
watch-hash :watch_hash}
(db-bounties/open-bounty-contracts)]
(update-bounty-token-balances issue-id bounty-address watch-hash)))
(bounties/watch-tokens issue-id bounty-address watch-hash)))
(log/info "Exit update-contract-internal-balances"))
(defn get-bounty-funds

View File

@ -399,12 +399,30 @@
(defn strip-0x [x]
(str/replace x #"^0x" ""))
(reg-event-fx
:watch-and-confirm-payout
(fn [{:keys [db]} [_ {issue-id :issue_id
contract-address :contract_address
:as issue}]]
{:db (assoc-in db [:owner-bounties issue-id :confirming?] true)
:http {:method POST
:url "/api/watchTokens"
:params {:contract_address contract-address
:issue_id issue-id}
:on-success #(dispatch [:confirm-payout issue])
:on-error #(do
(dispatch [:payout-confirm-failed issue-id %1])
(dispatch [:set-flash-message
:error
(str "Failed to execute watch " %1)]))}}))
(reg-event-fx
:confirm-payout
(fn [{:keys [db]} [_ {issue-id :issue_id
owner-address :owner_address
contract-address :contract_address
confirm-hash :confirm_hash} issue]]
confirm-hash :confirm_hash
:as issue}]]
(println (:web3 db))
(let [w3 (:web3 db)
confirm-method-id (sig->method-id w3 "confirmTransaction(uint256)")
@ -417,11 +435,10 @@
:gas-price 20000000000
:value 0
:data data}]
(println "data:" data)
(try
(web3-eth/send-transaction! w3 payload
(send-transaction-callback issue-id))
{:db (assoc-in db [:owner-bounties issue-id :confirming?] true)}
{:db db}
(catch js/Error e
{:db (assoc-in db [:owner-bounties issue-id :confirm-failed?] true)
:dispatch-n [[:payout-confirm-failed issue-id e]

View File

@ -38,7 +38,7 @@
(merge (if (and merged? (not paid?))
{}
{:disabled true})
{:on-click #(rf/dispatch [:confirm-payout claim])}
{:on-click #(rf/dispatch [:watch-and-confirm-payout claim])}
(when (and (or confirming? bot-confirm-unmined?)
merged?)
{:class "busy loading" :disabled true}))