Merge pull request #427 from status-im/fix/nonce-increment
Send txs sequentially; add thread to check for unmined txs
This commit is contained in:
commit
5e4a4bfbb9
|
@ -145,35 +145,11 @@ RETURNING repo_id, issue_id, issue_number, title, commit_sha, contract_address;
|
|||
-- :name update-transaction-hash :! :n
|
||||
-- :doc updates transaction-hash for a given issue
|
||||
UPDATE issues
|
||||
SET transaction_hash = :transaction_hash
|
||||
SET transaction_hash = :transaction_hash,
|
||||
updated = timezone('utc'::text, now())
|
||||
WHERE issue_id = :issue_id;
|
||||
|
||||
|
||||
-- TODO: this is terrible
|
||||
-- :name update-contract-address :<! :1
|
||||
-- :doc updates contract-address for a given issue
|
||||
WITH t AS (
|
||||
SELECT
|
||||
i.issue_id AS issue_id,
|
||||
i.issue_number AS issue_number,
|
||||
i.title AS title,
|
||||
i.transaction_hash AS transaction_hash,
|
||||
i.contract_address AS contract_address,
|
||||
i.comment_id AS comment_id,
|
||||
i.repo_id AS repo_id,
|
||||
r.owner AS owner,
|
||||
r.repo AS repo
|
||||
FROM issues i, repositories r
|
||||
WHERE r.repo_id = i.repo_id
|
||||
AND i.issue_id = :issue_id
|
||||
)
|
||||
UPDATE issues i
|
||||
SET contract_address = :contract_address,
|
||||
updated = timezone('utc'::text, now())
|
||||
FROM t
|
||||
WHERE i.issue_id = :issue_id
|
||||
RETURNING t.issue_id, t.issue_number, t.title, t.transaction_hash, t.comment_id, i.contract_address, t.owner, t.repo, t.repo_id;
|
||||
|
||||
-- :name update-comment-id :! :n
|
||||
-- :doc updates comment-id for a given issue
|
||||
UPDATE issues
|
||||
|
@ -241,11 +217,61 @@ WHERE pr_id = :pr_id;
|
|||
-- Bounties ------------------------------------------------------------------------
|
||||
|
||||
|
||||
-- :name unmined-txs :? :*
|
||||
-- :doc hashes that haven't been mined for some time
|
||||
SELECT
|
||||
CASE WHEN transaction_hash is not null and contract_address is null
|
||||
THEN transaction_hash
|
||||
WHEN execute_hash is not null and confirm_hash is null
|
||||
THEN execute_hash
|
||||
WHEN watch_hash is not null
|
||||
THEN watch_hash
|
||||
END
|
||||
AS tx_hash,
|
||||
CASE WHEN transaction_hash is not null and contract_address is null
|
||||
THEN 'deploy'
|
||||
WHEN execute_hash is not null and confirm_hash is null
|
||||
THEN 'execute'
|
||||
WHEN watch_hash is not null
|
||||
THEN 'watch'
|
||||
END
|
||||
AS type,
|
||||
issue_id
|
||||
FROM issues
|
||||
WHERE updated < timezone('utc'::text, now()) - interval '10 minutes'
|
||||
AND (transaction_hash is not null and contract_address is null
|
||||
OR execute_hash is not null and confirm_hash is null
|
||||
OR watch_hash is not null);
|
||||
|
||||
-- :name save-tx-info! :! :n
|
||||
-- :doc save tx hash from receipt
|
||||
UPDATE issues
|
||||
SET
|
||||
--~ (when (= (:type params) "deploy") "transaction_hash")
|
||||
--~ (when (= (:type params) "execute") "execute_hash")
|
||||
--~ (when (= (:type params) "watch") "watch_hash")
|
||||
= :tx-hash,
|
||||
updated = timezone('utc'::text, now())
|
||||
WHERE issue_id=:issue-id
|
||||
|
||||
|
||||
-- :name save-tx-result! :! :n
|
||||
-- :doc save tx hash from receipt
|
||||
UPDATE issues
|
||||
SET
|
||||
--~ (when (= (:type params) "deploy") "contract_address")
|
||||
--~ (when (= (:type params) "execute") "confirm_hash")
|
||||
--~ (when (= (:type params) "watch") "watch_hash")
|
||||
= :result,
|
||||
updated = timezone('utc'::text, now())
|
||||
WHERE issue_id=:issue-id
|
||||
|
||||
-- :name pending-contracts :? :*
|
||||
-- :doc bounty issues where deploy contract has failed
|
||||
SELECT
|
||||
i.issue_id AS issue_id,
|
||||
i.issue_number AS issue_number,
|
||||
i.transaction_hash AS transaction_hash,
|
||||
r.owner AS owner,
|
||||
u.address AS owner_address,
|
||||
r.repo AS repo
|
||||
|
@ -322,31 +348,11 @@ AND u.id = p.user_id
|
|||
AND i.payout_receipt IS NULL
|
||||
AND i.payout_hash IS NOT NULL;
|
||||
|
||||
-- :name update-confirm-hash :! :n
|
||||
-- :doc updates issue with confirmation hash
|
||||
UPDATE issues
|
||||
SET confirm_hash = :confirm_hash,
|
||||
updated = timezone('utc'::text, now())
|
||||
WHERE issue_id = :issue_id;
|
||||
|
||||
-- :name update-execute-hash :! :n
|
||||
-- :doc updates issue with execute transaction hash
|
||||
UPDATE issues
|
||||
SET execute_hash = :execute_hash,
|
||||
updated = timezone('utc'::text, now())
|
||||
WHERE issue_id = :issue_id;
|
||||
|
||||
-- :name update-winner-login :! :n
|
||||
UPDATE issues
|
||||
SET winner_login = :winner_login
|
||||
WHERE issue_id = :issue_id;
|
||||
|
||||
-- :name update-watch-hash :! :n
|
||||
-- :doc updates issue with watch transaction hash
|
||||
UPDATE issues
|
||||
SET watch_hash = :watch_hash
|
||||
WHERE issue_id = :issue_id;
|
||||
|
||||
-- :name pending-watch-calls :? :*
|
||||
-- :doc issues with a pending watch transaction
|
||||
SELECT
|
||||
|
@ -415,6 +421,23 @@ FROM issues
|
|||
WHERE repo_id = :repo_id
|
||||
AND issue_number = :issue_number;
|
||||
|
||||
-- :name get-issue-by-id :? :1
|
||||
-- :doc get issue from DB by issue-id
|
||||
SELECT
|
||||
i.issue_id AS issue_id,
|
||||
i.issue_number AS issue_number,
|
||||
i.is_open AS is_open,
|
||||
i.winner_login AS winner_login,
|
||||
i.commit_sha AS commit_sha,
|
||||
i.title AS title,
|
||||
i.comment_id AS comment_id,
|
||||
i.repo_id AS repo_id,
|
||||
r.owner AS owner,
|
||||
r.repo AS repo
|
||||
FROM issues i, repositories r
|
||||
WHERE r.repo_id = i.repo_id
|
||||
AND i.issue_id = :issue-id
|
||||
|
||||
|
||||
|
||||
-- :name open-bounties :? :*
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
[commiteth.db.repositories :as repos]
|
||||
[commiteth.db.comment-images :as comment-images]
|
||||
[commiteth.eth.core :as eth]
|
||||
[commiteth.eth.tracker :as tracker]
|
||||
[commiteth.github.core :as github]
|
||||
[commiteth.eth.multisig-wallet :as multisig]
|
||||
[commiteth.model.bounty :as bnt]
|
||||
|
@ -21,23 +22,18 @@
|
|||
(let [labels (:labels issue)]
|
||||
(some #(= label-name (:name %)) labels)))
|
||||
|
||||
(defn deploy-contract [owner owner-address repo issue-id issue-number]
|
||||
(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
|
||||
(log/infof "issue %s: Deploying contract to %s" issue-id owner-address)
|
||||
(if-let [transaction-hash (multisig/deploy-multisig {:owner owner-address
|
||||
:internal-tx-id (str "contract-github-issue-" issue-id)})]
|
||||
(if-let [tx-info (multisig/deploy-multisig {:owner owner-address
|
||||
:internal-tx-id [:deploy issue-id]})]
|
||||
(do
|
||||
(log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id transaction-hash)
|
||||
(let [resp (github/post-deploying-comment owner
|
||||
repo
|
||||
issue-number
|
||||
transaction-hash)
|
||||
comment-id (:id resp)]
|
||||
(log/infof "issue %s: post-deploying-comment response: %s" issue-id resp)
|
||||
(issues/update-comment-id issue-id comment-id))
|
||||
(issues/update-transaction-hash issue-id transaction-hash))
|
||||
(log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id (:tx-hash tx-info))
|
||||
(github/post-deploying-comment issue-id
|
||||
(:tx-hash tx-info))
|
||||
(tracker/track-tx! tx-info))
|
||||
(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)))))
|
||||
|
||||
|
@ -51,7 +47,7 @@
|
|||
(log/debug "issue %s: Adding bounty for issue %s/%s - owner address: %s"
|
||||
issue-id repo issue-number owner-address)
|
||||
(if (= 1 created-issue)
|
||||
(deploy-contract owner owner-address repo issue-id issue-number)
|
||||
(deploy-contract owner-address issue-id)
|
||||
(log/debug "issue %s: Issue already exists in DB, ignoring"))))
|
||||
|
||||
(defn maybe-add-bounty-for-issue [repo repo-id issue]
|
||||
|
|
|
@ -40,26 +40,12 @@
|
|||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/confirmed-payouts con-db)))
|
||||
|
||||
(defn update-confirm-hash
|
||||
[issue-id confirm-hash]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-confirm-hash con-db {:issue_id issue-id :confirm_hash confirm-hash})))
|
||||
|
||||
(defn update-execute-hash
|
||||
[issue-id execute-hash]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-execute-hash con-db {:issue_id issue-id :execute_hash execute-hash})))
|
||||
|
||||
(defn update-winner-login
|
||||
[issue-id login]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-winner-login con-db {:issue_id issue-id :winner_login login})))
|
||||
|
||||
(defn update-watch-hash
|
||||
[issue-id watch-hash]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-watch-hash con-db {:issue_id issue-id :watch_hash watch-hash})))
|
||||
|
||||
(defn pending-watch-calls
|
||||
[]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
|
@ -100,3 +86,5 @@
|
|||
[]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/bounties-activity con-db)))
|
||||
|
||||
|
||||
|
|
|
@ -38,19 +38,25 @@
|
|||
:title title})))
|
||||
|
||||
|
||||
(defn update-transaction-hash
|
||||
"Updates issue with transaction-hash"
|
||||
[issue-id transaction-hash]
|
||||
(defn save-tx-info!
|
||||
"Set transaction_hash, execute_hash or watch_hash depending on operation"
|
||||
[issue-id tx-hash type-kw]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-transaction-hash con-db {:issue_id issue-id
|
||||
:transaction_hash transaction-hash})))
|
||||
(db/save-tx-info! con-db {:issue-id issue-id
|
||||
:tx-hash tx-hash
|
||||
:type (name type-kw)})))
|
||||
|
||||
(defn update-contract-address
|
||||
"Updates issue with contract-address"
|
||||
[issue-id contract-address]
|
||||
(defn save-tx-result!
|
||||
"Set contract_address, confirm_hash or watch_hash depending on operation"
|
||||
[issue-id result type-kw]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-contract-address con-db {:issue_id issue-id
|
||||
:contract_address contract-address})))
|
||||
(db/save-tx-result! con-db {:issue-id issue-id
|
||||
:result result
|
||||
:type (name type-kw)})))
|
||||
|
||||
(defn unmined-txs []
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/unmined-txs con-db)))
|
||||
|
||||
(defn update-comment-id
|
||||
"Updates issue with comment id"
|
||||
|
@ -103,3 +109,8 @@
|
|||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/get-issue con-db {:repo_id repo-id
|
||||
:issue_number issue-number})))
|
||||
|
||||
(defn get-issue-by-id
|
||||
[issue-id]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/get-issue-by-id con-db {:issue-id issue-id})))
|
||||
|
|
|
@ -3,23 +3,15 @@
|
|||
[org.httpkit.client :refer [post]]
|
||||
[clojure.java.io :as io]
|
||||
[commiteth.config :refer [env]]
|
||||
[commiteth.eth.web3j :refer [get-signed-tx]]
|
||||
[clojure.string :refer [join]]
|
||||
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
|
||||
[clojure.tools.logging :as log]
|
||||
[commiteth.eth.tracker :as tracker]
|
||||
[clj-time.core :as t]
|
||||
[clojure.string :as str]
|
||||
[mount.core :as mount]
|
||||
[pandect.core :as pandect]
|
||||
[commiteth.util.util :refer [json-api-request]])
|
||||
(:import [org.web3j
|
||||
protocol.Web3j
|
||||
protocol.http.HttpService
|
||||
protocol.core.DefaultBlockParameterName
|
||||
protocol.core.methods.response.EthGetTransactionCount
|
||||
protocol.core.methods.request.RawTransaction
|
||||
utils.Numeric
|
||||
crypto.Credentials
|
||||
crypto.TransactionEncoder
|
||||
crypto.WalletUtils]))
|
||||
[commiteth.util.util :refer [json-api-request]]))
|
||||
|
||||
(defn eth-rpc-url [] (env :eth-rpc-url "http://localhost:8545"))
|
||||
(defn eth-account [] (:eth-account env))
|
||||
|
@ -28,83 +20,6 @@
|
|||
(defn auto-gas-price? [] (env :auto-gas-price false))
|
||||
(defn offline-signing? [] (env :offline-signing true))
|
||||
|
||||
(def web3j-obj
|
||||
(delay (Web3j/build (HttpService. (eth-rpc-url)))))
|
||||
|
||||
(def creds-obj (atom nil))
|
||||
|
||||
(defn wallet-file-path []
|
||||
(env :eth-wallet-file))
|
||||
|
||||
(defn wallet-password []
|
||||
(env :eth-password))
|
||||
|
||||
(defn creds []
|
||||
(or @creds-obj
|
||||
(let [password (wallet-password)
|
||||
file-path (wallet-file-path)]
|
||||
(if (and password file-path)
|
||||
(swap! creds-obj
|
||||
(constantly (WalletUtils/loadCredentials
|
||||
password
|
||||
file-path)))
|
||||
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
|
||||
{:password password :file-path file-path}))))))
|
||||
|
||||
(defn get-web3j-nonce [web3j-instance]
|
||||
(.. (.ethGetTransactionCount web3j-instance (env :eth-account) DefaultBlockParameterName/LATEST)
|
||||
sendAsync
|
||||
get
|
||||
getTransactionCount))
|
||||
|
||||
(defprotocol INonceTracker
|
||||
"The reason we need this is that we might send mutliple identical
|
||||
transactions (e.g. bounty contract deploy with same owner) shortly
|
||||
after another. In this case web3j's TX counting only increases once
|
||||
a transaction has been confirmed and so we send multiple identical
|
||||
transactions with the same nonce.
|
||||
|
||||
Web3j also provides a TransactionManager that increases nonces but it
|
||||
does not track nonces related to transactions and so as far as I understand
|
||||
it might cause transactions to be executed twice if they are retried.
|
||||
|
||||
https://github.com/web3j/web3j/blob/d19855475aa6620a7e93523bd9ede26ca50ed042/core/src/main/java/org/web3j/tx/RawTransactionManager.java"
|
||||
(get-nonce [this internal-tx-id]
|
||||
"Return the to be used nonce for an OpenBounty Ethereum
|
||||
transaction identified by `internal-tx-id`. As these IDs are stable
|
||||
we can use them to use consistent nonces for the same transaction."))
|
||||
|
||||
(defrecord NonceTracker [state]
|
||||
INonceTracker
|
||||
(get-nonce [this internal-tx-id]
|
||||
(let [prev-nonce (get @state internal-tx-id)
|
||||
web3j-nonce (get-web3j-nonce @web3j-obj)
|
||||
nonces (set (vals @state))
|
||||
nonce (if (seq nonces)
|
||||
(inc (apply max nonces))
|
||||
web3j-nonce)]
|
||||
(when prev-nonce
|
||||
(log/warnf "%s: tx will be retried (prev-nonce: %s, new-nonce: %s, web3j-nonce: %s)"
|
||||
internal-tx-id prev-nonce nonce web3j-nonce))
|
||||
;; TODO this is a memory leak since tracking state is never pruned
|
||||
;; Since we're not doing 1000s of transactions every day yet we can
|
||||
;; probably defer worrying about this until a bit later
|
||||
(swap! state assoc internal-tx-id nonce)
|
||||
nonce)))
|
||||
|
||||
(def nonce-tracker
|
||||
(->NonceTracker (atom {})))
|
||||
|
||||
(defn get-signed-tx [{:keys [gas-price gas-limit to data internal-tx-id]}]
|
||||
"Create a sign a raw transaction. 'From' argument is not needed as it's already encoded in credentials.
|
||||
See https://web3j.readthedocs.io/en/latest/transactions.html#offline-transaction-signing"
|
||||
(let [nonce (get-nonce nonce-tracker internal-tx-id)]
|
||||
(log/infof "%s: Signing nonce: %s, gas-price: %s, gas-limit: %s"
|
||||
internal-tx-id nonce gas-price gas-limit)
|
||||
(-> (RawTransaction/createTransaction (biginteger nonce) gas-price gas-limit to data)
|
||||
(TransactionEncoder/signMessage (creds))
|
||||
(Numeric/toHexString))))
|
||||
|
||||
(defn eth-gasstation-gas-price
|
||||
"Get max of average and average_calc from gas station and use that
|
||||
as gas price. average_calc is computed from a larger time period than average,
|
||||
|
@ -155,7 +70,9 @@
|
|||
(defn eth-rpc
|
||||
[{:keys [method params internal-tx-id]}]
|
||||
{:pre [(string? method) (some? params)]}
|
||||
(let [request-id (swap! req-id-tracker inc)
|
||||
(let [[type-kw issue-id] internal-tx-id
|
||||
tx-id-str (str type-kw "-" issue-id)
|
||||
request-id (swap! req-id-tracker inc)
|
||||
body {:jsonrpc "2.0"
|
||||
:method method
|
||||
:params params
|
||||
|
@ -165,21 +82,22 @@
|
|||
response @(post (eth-rpc-url) options)
|
||||
result (safe-read-str (:body response))]
|
||||
(when internal-tx-id
|
||||
(log/infof "%s: eth-rpc %s" internal-tx-id method))
|
||||
(log/debugf "%s: eth-rpc req(%s) body: %s" internal-tx-id request-id body)
|
||||
(if internal-tx-id
|
||||
(log/infof "%s: eth-rpc req(%s) result: %s" internal-tx-id request-id result)
|
||||
(log/infof "%s: eth-rpc %s" tx-id-str method))
|
||||
(log/debugf "%s: eth-rpc req(%s) body: %s" tx-id-str request-id body)
|
||||
(if tx-id-str
|
||||
(log/infof "%s: eth-rpc req(%s) result: %s" tx-id-str request-id result)
|
||||
(log/debugf "no-tx-id: eth-rpc req(%s) result: %s" request-id result))
|
||||
(cond
|
||||
;; Ignore any responses that have mismatching request ID
|
||||
(not= (:id result) request-id)
|
||||
(log/error "Geth returned an invalid json-rpc request ID, ignoring response")
|
||||
(throw (ex-info "Geth returned an invalid json-rpc request ID, ignoring response"
|
||||
{:result result}))
|
||||
|
||||
;; If request ID matches but contains error, throw
|
||||
(:error result)
|
||||
(throw
|
||||
(ex-info (format "%s: Error submitting transaction via eth-rpc %s"
|
||||
(or internal-tx-id "(no-tx-id)") (:error result))
|
||||
(or tx-id-str "(no-tx-id)") (:error result))
|
||||
(:error result)))
|
||||
|
||||
:else
|
||||
|
@ -277,36 +195,70 @@
|
|||
(eth-rpc {:method "eth_call"
|
||||
:params [{:to contract :data data} "latest"]})))
|
||||
|
||||
(defn execute
|
||||
[{:keys [from contract method-id gas-limit params internal-tx-id]}]
|
||||
{:pre [(string? method-id)]}
|
||||
(defn construct-params
|
||||
[{:keys [from contract method-id params gas-price]}]
|
||||
(let [data (apply format-call-params method-id params)
|
||||
gas-price (gas-price)
|
||||
value (format "0x%x" 0)
|
||||
params (cond-> {:data data
|
||||
value (format "0x%x" 0)]
|
||||
(cond-> {:data data
|
||||
:from from
|
||||
:value value}
|
||||
gas-price
|
||||
(merge {:gasPrice (integer->hex gas-price)})
|
||||
contract
|
||||
(merge {:to contract}))
|
||||
gas (or gas-limit (estimate-gas from contract value params))
|
||||
params (if (offline-signing?)
|
||||
(get-signed-tx {:internal-tx-id internal-tx-id
|
||||
:gas-price (biginteger gas-price)
|
||||
:gas-limit (hex->big-integer gas)
|
||||
:to contract
|
||||
:data data})
|
||||
(assoc params :gas gas))]
|
||||
(if (offline-signing?)
|
||||
(merge {:to contract}))))
|
||||
|
||||
(defmulti execute (fn [_] (if (offline-signing?)
|
||||
:with-tx-signing
|
||||
:no-tx-signing)))
|
||||
|
||||
|
||||
(defmethod execute :with-tx-signing
|
||||
[{:keys [from contract method-id gas-limit params internal-tx-id]
|
||||
:as args}]
|
||||
{:pre [(string? method-id)]}
|
||||
(let [[type-kw issue-id] internal-tx-id
|
||||
nonce (tracker/try-reserve-nonce!)
|
||||
gas-price (gas-price)
|
||||
params (construct-params (assoc args :gas-price gas-price))
|
||||
gas (or gas-limit (estimate-gas from contract (:value params) params))
|
||||
params (get-signed-tx (biginteger gas-price)
|
||||
(hex->big-integer gas)
|
||||
contract
|
||||
(:data params)
|
||||
nonce)
|
||||
tx-hash (try
|
||||
(eth-rpc
|
||||
{:method "eth_sendRawTransaction"
|
||||
:params [params]
|
||||
:internal-tx-id internal-tx-id})
|
||||
(eth-rpc
|
||||
(catch Throwable ex
|
||||
(tracker/drop-nonce! nonce)
|
||||
(throw ex)))]
|
||||
{:tx-hash tx-hash
|
||||
:issue-id issue-id
|
||||
:type type-kw
|
||||
:nonce nonce
|
||||
:timestamp (t/now)}))
|
||||
|
||||
(defmethod execute :no-tx-signing
|
||||
[{:keys [from contract method-id gas-limit params internal-tx-id]
|
||||
:as args}]
|
||||
{:pre [(string? method-id)]}
|
||||
(let [[type-kw issue-id] internal-tx-id
|
||||
gas-price (gas-price)
|
||||
params (construct-params (assoc args :gas-price gas-price))
|
||||
gas (or gas-limit (estimate-gas from contract (:value params) params))
|
||||
params (assoc params :gas gas)
|
||||
tx-hash (eth-rpc
|
||||
{:method "personal_sendTransaction"
|
||||
:params [params (eth-password)]
|
||||
:internal-tx-id internal-tx-id}))))
|
||||
:internal-tx-id internal-tx-id})]
|
||||
{:tx-hash tx-hash
|
||||
:type type-kw
|
||||
:timestamp (t/now)
|
||||
:issue-id issue-id}
|
||||
))
|
||||
|
||||
|
||||
(defn hex-ch->num
|
||||
[ch]
|
||||
|
@ -362,14 +314,3 @@
|
|||
(filter true?)
|
||||
(empty?)))
|
||||
true))))
|
||||
|
||||
(mount/defstate
|
||||
eth-core
|
||||
:start
|
||||
(do
|
||||
(swap! creds-obj (constantly nil))
|
||||
(log/info "eth/core started"))
|
||||
:stop
|
||||
(log/info "eth/core stopped"))
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(:require [commiteth.eth.core :as eth]
|
||||
[commiteth.config :refer [env]]
|
||||
[clojure.tools.logging :as log]
|
||||
[commiteth.eth.web3j :refer [web3j-obj creds-obj]]
|
||||
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
|
||||
[commiteth.eth.token-data :as token-data])
|
||||
(:import [org.web3j
|
||||
|
@ -41,7 +42,7 @@
|
|||
`internal-tx-id` is used to identify what issue this multisig is deployed
|
||||
for and manage nonces at a later point in time."
|
||||
[{:keys [owner internal-tx-id]}]
|
||||
{:pre [(string? owner) (string? internal-tx-id)]}
|
||||
{:pre [(string? owner) (vector? internal-tx-id)]}
|
||||
(eth/execute {:internal-tx-id internal-tx-id
|
||||
:from (eth/eth-account)
|
||||
:contract (factory-contract-addr)
|
||||
|
@ -87,7 +88,7 @@
|
|||
|
||||
(defn send-all
|
||||
[{:keys [contract payout-address internal-tx-id]}]
|
||||
{:pre [(string? contract) (string? payout-address) (string? internal-tx-id)]}
|
||||
{:pre [(string? contract) (string? payout-address) (vector? internal-tx-id)]}
|
||||
(log/debug "multisig/send-all " contract payout-address internal-tx-id)
|
||||
(let [params (eth/format-call-params
|
||||
(:withdraw-everything method-ids)
|
||||
|
@ -106,11 +107,11 @@
|
|||
(:address token-details)))
|
||||
|
||||
(defn watch-token
|
||||
[bounty-addr token]
|
||||
[{:keys [bounty-addr token internal-tx-id]}]
|
||||
(log/debug "multisig/watch-token" bounty-addr token)
|
||||
(let [token-address (get-token-address token)]
|
||||
(assert token-address)
|
||||
(eth/execute {:internal-tx-id (str "watch-token-" (System/currentTimeMillis) "-" bounty-addr)
|
||||
(eth/execute {:internal-tx-id internal-tx-id
|
||||
:from (eth/eth-account)
|
||||
:contract bounty-addr
|
||||
:method-id (:watch method-ids)
|
||||
|
@ -119,8 +120,8 @@
|
|||
|
||||
(defn load-bounty-contract [addr]
|
||||
(MultiSigTokenWallet/load addr
|
||||
@eth/web3j-obj
|
||||
(eth/creds)
|
||||
@web3j-obj
|
||||
@creds-obj
|
||||
(eth/gas-price)
|
||||
(BigInteger/valueOf 500000)))
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns commiteth.eth.token-registry
|
||||
(:require [commiteth.eth.core :as eth]
|
||||
[commiteth.config :refer [env]]
|
||||
[commiteth.eth.web3j :refer [web3j-obj creds-obj]]
|
||||
[clojure.tools.logging :as log])
|
||||
(:import [org.web3j
|
||||
abi.datatypes.generated.Uint256
|
||||
|
@ -22,8 +23,8 @@
|
|||
|
||||
(defn- load-tokenreg-contract [addr]
|
||||
(TokenReg/load addr
|
||||
@eth/web3j-obj
|
||||
(eth/creds)
|
||||
@web3j-obj
|
||||
@creds-obj
|
||||
(eth/gas-price)
|
||||
(BigInteger/valueOf 21000)))
|
||||
|
||||
|
@ -58,8 +59,8 @@
|
|||
(defn deploy-parity-tokenreg
|
||||
"Deploy an instance of parity token-registry to current network"
|
||||
[]
|
||||
(TokenReg/deploy @eth/web3j-obj
|
||||
(eth/creds)
|
||||
(TokenReg/deploy @web3j-obj
|
||||
@creds-obj
|
||||
(eth/gas-price)
|
||||
(BigInteger/valueOf 4000000) ;; gas limit
|
||||
BigInteger/ZERO))
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
(ns commiteth.eth.tracker
|
||||
(:require [clojure.data.json :as json]
|
||||
[org.httpkit.client :refer [post]]
|
||||
[commiteth.db.issues :as issues]
|
||||
[commiteth.config :refer [env]]
|
||||
[commiteth.eth.web3j :refer [web3j-obj]]
|
||||
[clojure.tools.logging :as log]
|
||||
[clojure.string :as str]
|
||||
[clj-time.core :as t])
|
||||
(:import [org.web3j
|
||||
protocol.Web3j
|
||||
protocol.http.HttpService
|
||||
protocol.core.DefaultBlockParameterName]))
|
||||
|
||||
(defn eth-rpc-url [] (env :eth-rpc-url "http://localhost:8545"))
|
||||
|
||||
(defn get-nonce []
|
||||
(.. (.ethGetTransactionCount @web3j-obj
|
||||
(env :eth-account)
|
||||
DefaultBlockParameterName/PENDING)
|
||||
sendAsync
|
||||
get
|
||||
getTransactionCount))
|
||||
|
||||
(defprotocol ITxTracker
|
||||
(try-reserve-nonce [this]
|
||||
"Fetch current nonce via eth_getTransactionCount
|
||||
and either return it if it is not in use yet,
|
||||
or throw an exception")
|
||||
(drop-nonce [this nonce]
|
||||
"If tx execution returned with an error,
|
||||
release previously reserved nonce")
|
||||
(track-tx [this tx-info]
|
||||
"Record tx data after successful submission")
|
||||
(untrack-tx [this tx-info]
|
||||
"Mark tx as successfully mined")
|
||||
(prune-txs [this txs]
|
||||
"Release nonces related to txs param
|
||||
and the ones that have expired (haven't been mined after a certain timeout)")
|
||||
)
|
||||
|
||||
(defrecord SequentialTxTracker [current-tx]
|
||||
ITxTracker
|
||||
(try-reserve-nonce [this]
|
||||
(let [nonce (get-nonce)]
|
||||
(if (or (nil? @current-tx)
|
||||
(> nonce (:nonce @current-tx)))
|
||||
(:nonce (reset! current-tx {:nonce nonce}))
|
||||
(throw (Exception. (str "Attempting to re-use old nonce" nonce))))))
|
||||
(drop-nonce [this nonce]
|
||||
(reset! current-tx nil))
|
||||
(track-tx [this tx-info]
|
||||
(reset! current-tx tx-info))
|
||||
(untrack-tx [this tx-info]
|
||||
(when (= (:nonce tx-info) (:nonce @current-tx))
|
||||
(reset! current-tx nil)))
|
||||
(prune-txs [this unmined-txs]
|
||||
(when (or ((set (map :tx-hash unmined-txs)) (:tx-hash @current-tx))
|
||||
(and (:timestamp @current-tx)
|
||||
(t/before? (:timestamp @current-tx) (t/minus (t/now) (t/minutes 10)))))
|
||||
(log/errorf "Current nonce unmined for 10 minutes, force reset. Tx hash: %s, type: %s"
|
||||
(:tx-hash @current-tx) (:type @current-tx))
|
||||
(reset! current-tx nil)))
|
||||
|
||||
)
|
||||
|
||||
(def tx-tracker (SequentialTxTracker. (atom nil)))
|
||||
|
||||
(defn try-reserve-nonce! []
|
||||
(try-reserve-nonce tx-tracker))
|
||||
|
||||
(defn drop-nonce! [nonce]
|
||||
(drop-nonce tx-tracker nonce))
|
||||
|
||||
(defn track-tx!
|
||||
"Store tx data in tx-tracker and DB"
|
||||
[{:keys [issue-id tx-hash type]
|
||||
:as tx-info}]
|
||||
(track-tx tx-tracker tx-info)
|
||||
(issues/save-tx-info! issue-id tx-hash type))
|
||||
|
||||
(defn untrack-tx!
|
||||
"Mark tx data stored in tx-tracker and DB as successfully mined"
|
||||
[{:keys [issue-id result type]
|
||||
:as tx-info}]
|
||||
(untrack-tx tx-tracker tx-info)
|
||||
(issues/save-tx-result! issue-id result type))
|
||||
|
||||
(defn prune-txs! [unmined-txs]
|
||||
"Release nonces related to unmined txs,
|
||||
and set relevant DB fields to null thereby
|
||||
marking them as candidates for re-execution"
|
||||
(doseq [{issue-id :issue_id
|
||||
tx-hash :tx_hash
|
||||
type :type} unmined-txs]
|
||||
(log/infof "issue %s: resetting tx operation: %s for hash: %s" issue-id type tx-hash)
|
||||
(issues/save-tx-info! issue-id nil type))
|
||||
(prune-txs tx-tracker unmined-txs))
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
(ns commiteth.eth.web3j
|
||||
(:require [commiteth.config :refer [env]]
|
||||
[clojure.tools.logging :as log])
|
||||
(:import [org.web3j
|
||||
protocol.Web3j
|
||||
protocol.http.HttpService
|
||||
protocol.core.methods.request.RawTransaction
|
||||
utils.Numeric
|
||||
crypto.TransactionEncoder
|
||||
crypto.WalletUtils]))
|
||||
|
||||
(defn eth-rpc-url [] (env :eth-rpc-url "http://localhost:8545"))
|
||||
|
||||
(def web3j-obj
|
||||
(delay (Web3j/build (HttpService. (eth-rpc-url)))))
|
||||
|
||||
(defn wallet-file-path []
|
||||
(env :eth-wallet-file))
|
||||
|
||||
(defn wallet-password []
|
||||
(env :eth-password))
|
||||
|
||||
(def creds-obj
|
||||
(delay
|
||||
(let [password (wallet-password)
|
||||
file-path (wallet-file-path)]
|
||||
(if (and password file-path)
|
||||
(WalletUtils/loadCredentials
|
||||
password
|
||||
file-path)
|
||||
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
|
||||
{:password password :file-path file-path}))))))
|
||||
|
||||
(defn get-signed-tx [gas-price gas-limit to data nonce]
|
||||
"Create a sign a raw transaction.
|
||||
'From' argument is not needed as it's already
|
||||
encoded in credentials.
|
||||
See https://web3j.readthedocs.io/en/latest/transactions.html#offline-transaction-signing"
|
||||
(log/infof "Signing TX: nonce: %s, gas-price: %s, gas-limit: %s, data: %s"
|
||||
nonce gas-price gas-limit data)
|
||||
(-> (RawTransaction/createTransaction nonce gas-price gas-limit to data)
|
||||
(TransactionEncoder/signMessage @creds-obj)
|
||||
(Numeric/toHexString)))
|
||||
|
||||
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
[clj-http.client :as http]
|
||||
[commiteth.config :refer [env]]
|
||||
[digest :refer [sha-256]]
|
||||
[commiteth.db.issues :as db-issues]
|
||||
[clojure.tools.logging :as log]
|
||||
[cheshire.core :as json]
|
||||
[clojure.string :as str])
|
||||
|
@ -271,13 +272,6 @@
|
|||
(learn-more-text))
|
||||
eth-balance-str payee-login))
|
||||
|
||||
|
||||
(defn post-deploying-comment
|
||||
[owner repo issue-number tx-id]
|
||||
(let [comment (generate-deploying-comment owner repo issue-number tx-id)]
|
||||
(log/info "Posting comment to" (str owner "/" repo "/" issue-number) ":" comment)
|
||||
(issues/create-comment owner repo issue-number comment (self-auth-params))))
|
||||
|
||||
(defn make-patch-request [end-point positional query]
|
||||
(let [{:keys [auth oauth-token]
|
||||
:as query} query
|
||||
|
@ -296,6 +290,24 @@
|
|||
:otp))]
|
||||
(assoc req :body (json/generate-string (or raw-query proper-query)))))
|
||||
|
||||
(defn post-deploying-comment
|
||||
[issue-id tx-id]
|
||||
(let [{owner :owner
|
||||
repo :repo
|
||||
issue-number :issue_number
|
||||
comment-id :comment_id} (db-issues/get-issue-by-id issue-id)
|
||||
comment (generate-deploying-comment owner repo issue-number tx-id) ]
|
||||
(log/info "Posting comment to" (str owner "/" repo "/" issue-number) ":" comment)
|
||||
(if 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)))
|
||||
(let [resp (issues/create-comment owner repo issue-number comment (self-auth-params))]
|
||||
(db-issues/update-comment-id issue-id (:id resp))
|
||||
(log/infof "issue %s: post-deploying-comment response: %s" issue-id resp)
|
||||
resp))))
|
||||
|
||||
(defn update-comment
|
||||
"Update comment for an open bounty issue"
|
||||
[owner repo comment-id issue-number contract-address eth-balance eth-balance-str tokens]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(: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]
|
||||
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
|
||||
|
@ -52,11 +53,14 @@
|
|||
(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 [issue (issues/update-contract-address issue-id contract-address)
|
||||
(let [_ (tracker/untrack-tx! {:issue-id issue-id
|
||||
:tx-hash transaction-hash
|
||||
:result contract-address
|
||||
:type :deploy})
|
||||
{owner :owner
|
||||
repo :repo
|
||||
comment-id :comment_id
|
||||
issue-number :issue_number} issue
|
||||
issue-number :issue_number} (issues/get-issue-by-id issue-id)
|
||||
balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)]
|
||||
(log/infof "issue %s: Updating comment image" issue-id)
|
||||
|
@ -90,13 +94,10 @@
|
|||
[]
|
||||
(p :deploy-pending-contracts
|
||||
(doseq [{issue-id :issue_id
|
||||
issue-number :issue_number
|
||||
owner :owner
|
||||
owner-address :owner_address
|
||||
repo :repo} (db-bounties/pending-contracts)]
|
||||
owner-address :owner_address} (db-bounties/pending-contracts)]
|
||||
(log/infof "issue %s: Trying to re-deploy failed bounty contract deployment" issue-id)
|
||||
(try
|
||||
(bounties/deploy-contract owner owner-address repo issue-id issue-number)
|
||||
(bounties/deploy-contract owner-address issue-id)
|
||||
(catch Throwable t
|
||||
(log/errorf t "issue %s: deploy-pending-contracts exception: %s" issue-id (ex-data t)))))))
|
||||
|
||||
|
@ -132,11 +133,11 @@
|
|||
tokens
|
||||
winner-login
|
||||
true))
|
||||
(let [execute-hash (multisig/send-all {:contract contract-address
|
||||
(let [tx-info (multisig/send-all {:contract contract-address
|
||||
:payout-address payout-address
|
||||
:internal-tx-id (str "payout-github-issue-" issue-id)})]
|
||||
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address payout-address execute-hash)
|
||||
(db-bounties/update-execute-hash issue-id execute-hash)
|
||||
:internal-tx-id [:execute issue-id]})]
|
||||
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address payout-address (:tx-hash tx-info))
|
||||
(tracker/track-tx! tx-info)
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
|
@ -162,7 +163,10 @@
|
|||
(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)
|
||||
(db-bounties/update-confirm-hash issue-id confirm-hash)))
|
||||
(tracker/untrack-tx! {:issue-id issue-id
|
||||
:tx-hash execute-hash
|
||||
:result confirm-hash
|
||||
:type :execute})))
|
||||
(catch Throwable ex
|
||||
(log/errorf ex "issue %s: update-confirm-hash exception:" issue-id)))))
|
||||
(log/info "Exit update-confirm-hash"))
|
||||
|
@ -177,7 +181,10 @@
|
|||
(log/infof "issue %s: pending watch call %s" issue-id watch-hash)
|
||||
(try
|
||||
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
|
||||
(db-bounties/update-watch-hash issue-id nil))
|
||||
(tracker/untrack-tx! {:issue-id issue-id
|
||||
:tx-hash watch-hash
|
||||
:result nil
|
||||
:type :watch}))
|
||||
(catch Throwable ex
|
||||
(log/errorf ex "issue %s: update-watch-hash exception:" issue-id))))))
|
||||
|
||||
|
@ -265,8 +272,10 @@
|
|||
(when (and (nil? watch-hash)
|
||||
(not= balance internal-balance))
|
||||
(log/infof "bounty %s: balances not in sync, calling watch" bounty-addr)
|
||||
(let [hash (multisig/watch-token bounty-addr tla)]
|
||||
(db-bounties/update-watch-hash issue-id hash)))))))
|
||||
(let [tx-info (multisig/watch-token {:bounty-addr bounty-addr
|
||||
:token tla
|
||||
:internal-tx-id [:watch issue-id]})]
|
||||
(tracker/track-tx! tx-info)))))))
|
||||
(catch Throwable ex
|
||||
(log/error ex "bounty %s: update-bounty-token-balances exception" bounty-addr))))
|
||||
(log/info "Exit update-bounty-token-balances"))
|
||||
|
@ -376,6 +385,13 @@
|
|||
(log/error ex "issue %s: update-balances exception" issue-id)))))
|
||||
(log/info "Exit update-balances"))
|
||||
|
||||
(defn check-tx-receipts
|
||||
"At all times, there should be no more than one unmined tx hash,
|
||||
as we are executing txs sequentially"
|
||||
[]
|
||||
(log/info "In check-tx-receipts")
|
||||
(tracker/prune-txs! (issues/unmined-txs))
|
||||
(log/info "Exit check-tx-receipts"))
|
||||
|
||||
(defn wrap-in-try-catch [func]
|
||||
(try
|
||||
|
@ -399,6 +415,7 @@
|
|||
update-confirm-hash
|
||||
update-payout-receipt
|
||||
update-watch-hash
|
||||
check-tx-receipts
|
||||
self-sign-bounty
|
||||
])
|
||||
(log/info "run-1-min-interval-tasks done")))
|
||||
|
|
Loading…
Reference in New Issue