Two-phase-proof-of-work as a solution to mining pools?

Do repost and rate:

Nowadays, Bitcoin is dominated by large mining pools. You can easily inspect some COINBASE transactions and see that most of them are not mined by individuals, but rather by big, centralized pools. From time to time, some solutions to address this problem are proposed, but usually they are not used in practice, because mining in pools is still more profitable than doing it in more decentralized way.

How to solve this problem? How to make it more profitable to solo mine cryptocurrencies than joining mining pools? The LUCK coin project developers are convinced they found a solution. They think the main reason why pools are created lies in mining algorithm, the famous and well-known proof-of-work. The whole concept is described in their whitepaper as a two-phase-proof-of-work.

As the title suggests, there are two phases: the first one, when everyone calculates luck, and the second one, when miners actually are mining final blocks and broadcasting them to the network. As the name suggests: the higher the luck, the easier the difficulty (and vice versa). The whole source code is based on Ethereum, so we can start our journey by comparing block structure in Ethereum and LUCK to see the differences and better understand how it works.

> fort.getBlock(0){  basis: "0x0",  difficulty: 1000,  difficultyAlpha: "0x0",  difficultyBeta: "0x0",  extraData: "0x532dd24cac0f2271047aafec9dda071b7bdaaa79407d0117fb63e7b8609ad3f1",  firstNonce: "0x0000000000000000",  gasLimit: 50000000,  gasUsed: 0,  hash: "0x9a8aacea47052ceb346c3a2ad43c6a65639ebef2d00b23da88062c3c63670c87",  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",  luck: "0x0",  miner: "0x00000000000000000000000000000000000000000000000000",  mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",  nonce: "0x000000000000004f",  number: 0,  parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",  secondNonce: "0x0000000000000000",  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",  size: 570,  stateRoot: "0x35f8247eadcc31f01ae79dc887ce849eaea6915d79b0a73704af3e92665ba7ff",  timestamp: 1595326929,  totalDifficulty: 1000,  transactions: [],  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",  uncles: []}
> eth.getBlock(0){  difficulty: 17179869184,  extraData: "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",  gasLimit: 5000,  gasUsed: 0,  hash: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",  miner: "0x0000000000000000000000000000000000000000",  mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",  nonce: "0x0000000000000042",  number: 0,  parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",  size: 540,  stateRoot: "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544",  timestamp: 0,  totalDifficulty: 17179869184,  transactions: [],  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",  uncles: []}

As we can see, many parts are similar. There are some additional fields in LUCK coin: basis, difficultyAlpha, difficultyBeta, firstNonce, luck and secondNonce. To better understand how they are used, we can open go-luck/consensus/tppow.go file.

In this file we have verifyHeader function that, as the name suggests, is used to verify if some header is correct or not. Inside we can see standard validation rules, identical to those in Ethereum. There are extra data checks, timestamp verification, and then we have typical difficulty checking that is commented out. Here it is handled a bit differently. After gas limit and block number verification, we can read something new, created especially for this coin.

We have basis and alpha, taken from calcParam function. This function is used to calculate these two parameters. They are both based on the previous block. If alpha is lower than beta, we decrease basis by 4%, if not, we increase basis by 5%. Then, if we generate blocks slower than in 20 seconds, we increase basis and alpha by 10%, if not, we decrease both of them by 10%. Based on that we can see that high basis means easy mining and low basis means difficult mining.

Next, in verifyHeader function we have VerifySeal call that finally verifies everything. First, we have a call to SealLuck. This function uses argon2d hashing function to compute hash from ParentHash, Coinbase, Time and nonce. Then, we have a check inside VerifySeal function if this hash meets DifficultyAlpha. If everything is fine, we can calculate beta using calcBeta function. Inside this function, beta is calculated as basis multiplied by a square of the maxLuck divided by a square of the difference between luck and maxLuck. As we mentioned before, high basis means easy mining, so the higher our luck, the lower the number we divide by.

Returning to our VerifySeal function: we verify if beta meets DifficultyBeta. If it is, we can finally calculate luck using calcLuck function. In this function, we take ParentHash, Coinbase, Time and nonce, as in SealLuck function, but this time we also take block Number. Similar to SealLuck, we also use argon2d hashing function here, but in the end we take the result modulo maxLuck. In this way we can get luck in range from zero to maxLuck - 1, which is important in calculating basis above, when we have to divide by non-zero number.

Later, in our VerifySeal function we check if luck is correct. If it is, we call SealBlock function. In this function, we take many fields in our block, because we validate here if the whole block is correct. We take: ParentHash, UncleHash, Coinbase, Root, TxHash, ReceiptHash, Bloom, Number, GasLimit, GasUsed, Time, Extra, Basis, Lucky, DifficultyAlpha, DifficultyBeta and nonce. We take all of these fields, invoke argon2d hashing function again and return the final block hash.

We are back again in our VerifySeal function. We check if our final block hash meets DifficultyBeta. If it is, we continue and calculate difficulty using calcDifficulty function. In this function we have two cases. There was a hard fork at block 39200. Before this block the difficulty is simply taken from the Lucky value. After this block it is handled differently: we have HashScale set to four coins. We take maximum 256-bit number set to all ones, we divide it by HashScale, multiply it by one million and divide by Basis. If our block meets this difficulty, it is considered as valid and added to the blockchain.

To sum up, there is some complexity added to block generation to make it two-phase. The intention was that the first phase depends on the number of nodes and the second phase is used to mine final blocks. But developers seems to ignore the fact that nobody is forced to mine this coin the way they coded it in the official client. There is no verification if the first phase really depends on the number of nodes. And that means people can spend more or less time mining the first or the second phase and discover some optimal values giving better results than those provided by the official client.

More than that: there is nothing that really prevents anyone from creating pools here! We can define a share as a final block after two phases with lower difficulty than calculated by calcDifficulty function. We can define miner reward for such share as blockReward * shareDifficulty / neededDifficulty. It is as simple as that. All miners can mine to some pool address using stratum protocol, the pool have to just distribute values needed in the block header. Then, by treating this coin the same as in one-phase proof-of-work, everything else can be set accordingly.

Also, the main problem with solo mining this coin is that you have to wait a long time to actually receive anything. You have to solo mine the whole valid block and compete with huge mining farms, mainly from China and other countries where the electricity costs are quite low. To solve this problem, all that is needed is giving everyone a chance to receive something below blockReward. Now it is done by some unofficial pools, but I hope developers will create some second-layer protocol to officially cover this problem and in this way make it more profitable than joining mining pools.

Regulation and Society adoption

Ждем новостей

Нет новых страниц

Следующая новость