Diagram flow
Explanations
When a new miner wants to join the mining pool, they first need to call the ask-to-join
function and provide their BTC address as a parameter. This function performs some checks to make sure that the miner is not already in the pool or in the waiting list, and then adds the miner to the waiting list by setting their map-is-waiting
value to true and adding their address to the waiting-list
variable.
Other miners in the pool can then vote on whether to accept or reject the new miner. To vote positively, they need to call the vote-positive-join-request
function and provide the address of the miner they want to vote on as a parameter. Similarly, to vote negatively, they call the vote-negative-join-request
function. These functions first check whether the miner being voted on is actually in the waiting list and whether the voter has already voted on this miner. If all checks pass, the voter's vote is recorded in the appropriate map-votes-accept-join
or map-votes-reject-join
map.
When the total number of positive votes for a miner reaches the threshold (which is integer of (N * 67%) where N is the total number of miners), the miner is accepted into the mining pool. The try-enter-pool
function is called by the miner being voted on to check if the threshold has been met. If it has, the miner is removed from the waiting list, added to the pending-list
variable, and their map-is-waiting
value is set to false. If the threshold has not been met, the function returns false.
Then, after 100 blocks have passed from the last time new miners were added to the pool, anyone can call the add-pending-miners-to-pool
function, which adds all the pending miners to the mining pool. This is done to have less distributed key generations (DKG) and helps greatly in the mining alogirtm which takes into account the average amount spent by a Bitcoin address in the last 6 blocks
. Using DKG the address will be totally different ( as the public key and private key are different as well ). So this process should not be done too often because the first 4 blocks after a DKG would mean very low changes at winning them, but still the need to invest the same amount as before.
Once the miners are added to the pool, the threshold for actions is recalculated, and mining can continue with the new set of miners.
Smart Contract
Copy ;; data stored
(define-constant err-already-joined (err u101))
(define-constant err-already-asked-to-join (err u102))
(define-constant err-not-asked-to-join (err u103))
(define-constant err-no-vote-permission (err u104))
(define-constant err-already-voted (err u105))
(define-constant err-list-length-exceeded (err u106))
(define-constant err-no-pending-miners (err u107))
(define-constant err-more-blocks-to-pass (err u108))
(define-map map-is-miner { address : principal } { value : bool })
(define-map map-block-joined { address : principal } { block-height : uint })
(define-map map-block-asked-to-join { address : principal } { value : uint })
(define-map map-is-waiting { address : principal } { value : bool })
(define-map map-votes-accept-join { address : principal } { value : uint })
(define-map map-votes-reject-join { address : principal } { value : uint })
(define-map map-join-request-voter { miner-to-vote : principal, voter : principal } { value : bool })
(define-data-var k uint u0)
(define-data-var n uint u1)
(define-data-var waiting-list ( list 100 principal) ( list ))
(define-data-var miners-list ( list 100 principal) ( list ))
(define-data-var pending-accept-list ( list 100 principal) ( list ))
(define-data-var last-join-done uint u1)
(define-data-var k-percentage uint u67)
(define-data-var miner-to-remove-votes-join principal tx-sender)
(define-data-var blocks-to-pass uint u100)
;; functions
(define-public (ask-to-join (btc-address principal))
(begin
(asserts! ( not (check-is-miner-now tx-sender)) err-already-joined)
(asserts! ( not (check-is-waiting-now tx-sender)) err-already-asked-to-join)
(map-set map-block-asked-to-join {address : tx-sender} {value : block-height})
(var-set waiting-list (unwrap-panic (as-max-len? (concat (var-get waiting-list) ( list tx-sender)) u100)))
(map-set map-is-waiting {address : tx-sender} {value : true})
(ok true)))
(define-public (vote-positive-join-request (miner-to-vote principal))
(begin
(asserts! (check-is-waiting-now miner-to-vote) err-not-asked-to-join) ;; map_is_waiting
(asserts! (unwrap! (check-is-miner-when-requested-join miner-to-vote) err-cant-unwrap-check-miner) err-no-vote-permission)
(asserts! (has-voted-join miner-to-vote) err-already-voted) ;; O(1)
(map-set map-join-request-voter
{miner-to-vote : miner-to-vote, voter : tx-sender}
{value : true})
( if (is-some ( get value (map-get? map-votes-accept-join {address : miner-to-vote})))
(map-set map-votes-accept-join {address : miner-to-vote} {value : ( + (unwrap-panic ( get value (map-get? map-votes-accept-join {address : miner-to-vote}))) u1)})
(map-set map-votes-accept-join {address : miner-to-vote} {value : u1}))
(ok true)))
(define-public (vote-negative-join-request (miner-to-vote principal))
(begin
(asserts! (check-is-waiting-now miner-to-vote) err-not-asked-to-join)
(asserts! (unwrap! (check-is-miner-when-requested-join miner-to-vote) err-cant-unwrap-check-miner) err-no-vote-permission)
(asserts! (has-voted-join miner-to-vote) err-already-voted)
(map-set map-join-request-voter
{miner-to-vote : miner-to-vote, voter : tx-sender}
{value : true})
( if (is-some ( get value (map-get? map-votes-reject-join {address : miner-to-vote})))
(map-set map-votes-reject-join {address : miner-to-vote} {value : ( + (unwrap-panic ( get value (map-get? map-votes-reject-join {address : miner-to-vote}))) u1)})
(map-set map-votes-reject-join {address : miner-to-vote} {value : u1}))
( some
( if (is-vote-rejected-join (unwrap-panic ( get value (map-get? map-votes-reject-join {address : miner-to-vote}))) (unwrap-panic (get-k-at-block-asked-to-join miner-to-vote)) (unwrap-panic (get-n-at-block-asked-to-join miner-to-vote)))
(reject-miner-in-pool miner-to-vote)
false))
(ok true)))
(define-private (accept-miner-in-pool (miner principal))
(begin
( let ((pending-accept-result (as-max-len? (concat (var-get pending-accept-list) ( list miner)) u100)))
(asserts! (is-some pending-accept-result) err-list-length-exceeded) ;; O(1)
(var-set miner-to-remove-votes-join miner)
(var-set waiting-list (unwrap-panic (as-max-len? (unwrap-panic (remove-principal-waiting-list miner)) u100))) ;; O(N)
(map-delete map-is-waiting {address : miner})
(clear-votes-map-join-vote miner)
(ok (var-set pending-accept-list (unwrap-panic pending-accept-result))))))
(define-private (reject-miner-in-pool (miner principal))
(begin
( let ((remove-result (unwrap-panic (remove-principal-waiting-list miner))))
(var-set miner-to-remove-votes-join miner)
(var-set waiting-list remove-result)
(map-delete map-is-waiting {address : miner})
(clear-votes-map-join-vote miner)
true)))
(define-private (clear-votes-map-join-vote (miner principal))
(begin
(map-delete map-votes-accept-join {address : (var-get miner-to-remove-votes-join)})
(map-delete map-votes-reject-join {address : (var-get miner-to-remove-votes-join)})
(map-delete map-block-asked-to-join {address : (var-get miner-to-remove-votes-join)})
( map remove-map-record-join-vote (var-get miners-list))))
(define-private (remove-map-record-join-vote (miner principal))
( if (is-some (map-get? map-join-request-voter {miner-to-vote : (var-get miner-to-remove-votes-join), voter : miner}))
(map-delete map-join-request-voter {miner-to-vote : (var-get miner-to-remove-votes-join), voter : miner})
false))
(define-private (is-in-voters-list (miner principal) (voters-list ( list 100 principal)))
(is-some (index-of voters-list miner)))
(define-private (has-voted-join (miner principal))
( not ( if (is-some ( get value (map-get? map-join-request-voter {miner-to-vote : miner, voter : tx-sender})))
(unwrap-panic ( get value (map-get? map-join-request-voter {miner-to-vote : miner, voter : tx-sender})))
false)))
(define-public (try-enter-pool)
(begin
(asserts! (is-some ( get value (map-get? map-votes-accept-join {address : tx-sender}))) err-not-asked-to-join)
( if (is-vote-accepted (unwrap-panic ( get value (map-get? map-votes-accept-join {address : tx-sender}))) (unwrap-panic (get-k-at-block-asked-to-join tx-sender)))
(accept-miner-in-pool tx-sender)
(ok false))))
(define-public (add-pending-miners-to-pool)
(begin
( let ((len-pending-accept-list (len (var-get pending-accept-list))))
(asserts! ( not (is-eq len-pending-accept-list u0)) err-no-pending-miners)
(asserts! (x-blocks-passed (var-get blocks-to-pass)) err-more-blocks-to-pass)
( map add-miner-to-pool (var-get pending-accept-list))
(asserts! (is-some (as-max-len? (concat (var-get miners-list) (var-get pending-accept-list)) u100)) err-list-length-exceeded)
(var-set miners-list (unwrap-panic (as-max-len? (concat (var-get miners-list) (var-get pending-accept-list)) u100)))
(var-set n ( + (var-get n) len-pending-accept-list))
(var-set pending-accept-list ( list ))
(var-set last-join-done block-height)
( some (update-threshold))
(ok true))))
(define-private (update-threshold)
(var-set k ( / ( * (var-get k-percentage) ( - (var-get n) u1)) u100)))
(define-public (add-miner-to-pool (miner principal))
(begin
(map-set map-is-miner {address : miner} {value : true})
(map-set map-block-joined {address : miner} {block-height : block-height})
(ok true)))
(define-private (x-blocks-passed (x uint))
( if ( >= ( - block-height (var-get last-join-done)) x)
true
false))
(define-private (get-k-at-block-asked-to-join (miner-to-vote principal))
(begin
(asserts! (is-some ( get value (map-get? map-block-asked-to-join {address : miner-to-vote}))) err-not-asked-to-join)
(at-block
(unwrap-panic
(get-block-info? id-header-hash
(unwrap-panic
( get value
(map-get? map-block-asked-to-join {address : miner-to-vote})))))
(ok (var-get k)))))
(define-private (get-n-at-block-asked-to-join (miner-to-vote principal))
(begin
(asserts! (is-some ( get value (map-get? map-block-asked-to-join {address : miner-to-vote}))) err-not-asked-to-join)
(at-block
(unwrap-panic
(get-block-info? id-header-hash
(unwrap-panic
( get value
(map-get? map-block-asked-to-join {address : miner-to-vote})))))
(ok (var-get n)))))