Pages:
Author

Topic: PPCoin Criticism / Security / etc (Read 5861 times)

legendary
Activity: 1205
Merit: 1010
December 01, 2012, 09:09:42 PM
#28

What do you do about PoS miners who report blockchains from the future? A lot of coin-age can be destroyed if we allow 2025 to reported as occurring tomorrow, even if only a tiny % of coins did the mining.


Block timestamp is subject to the same bitcoin protocol of max two hours in the future.
legendary
Activity: 1050
Merit: 1003
December 01, 2012, 01:20:56 AM
#27
PoS as implemented is by block number, not time, hence time attacks do not affect it.  It looks like you can just mine a PoS transaction at 1 diff after a certain number of blocks have passed

What I don't understand so much is how they're signed for securely
In PPCoin, the PoS contains both a coin-age element (value of inputs, block number) and a time-stamping element (time is used as a random number seed).

I am suggesting using the # of large txns as a random number seed rather than time. (i.e. take time out of the protocol entirely)

As far how blocks are signed securely, that is simple. You just need to sign with your private key showing that you control the relevant inputs. It is just like securely signing a txn except the txn mines a block.



legendary
Activity: 1050
Merit: 1003
December 01, 2012, 01:15:59 AM
#26
Replying from mobile, so no elaborate quoting.

On timing, I think it is true that you do not care about absolute time, you do care about approximate time of intervals however. With the transaction based timing mechanism you described I believe that block time would get shorter and shorter as the network was busier, not a steady flow. Setting and trying to maintain a regular block interval is important, IMHO, and should be a mechanism that is as far from being manipulated as possible.
The optimal timing probably depends on network characteristics and technology. It doesn't make much sense to fix a permanent timing in the protocol. At some point, if there is enough txn volume to pay for the bandwidth/storage and the scheduling technology is there, confirmations could be almost instantaneous.

For now, suppose we want to target a 10 iterations every 10 minutes. Just give clients the following instructions.

For every second (based on the client's private clock), push a txn to stir the pot with probability x, where x is some small number. If there are less then 5 iterations announced over the past 10 minutes, then increase x by 10%. If there are more than 15 iterations announced, then decrease x by 10%. If everyone does, this you will end up with about 1 iteration per minute. One individual could spend money to speed this up temporarily, but it won't help him in any significant way. Any small action, is countered by negative feedback. If the network is running smoothly, he is better off relying on others to do the work. Large actions are costly and infeasible unless you have a lot of coin.

I also think asking or encouraging folks to keep their coins online is a mistake. Even if everyone tries, there will be plenty of times that less than 50% of coins are offline, especially if folks want to protect them. So if the currency is successful the majority will disappear.
I don't agree with you at all here. The right approach is to make keeping coins online safe from significant theft. We need this anyways to solve the theft issues that plague bitcoin. How to do this is kind of orthogonal to the discussion here.

Briefly, it is not hard to implement limited keys that place periodic withdrawal limits on txns. This is what real-world banks do. These are the keys that need to be online. Keys that can do anything are like your ID and bank account book. You can keep those in a safe.
legendary
Activity: 1484
Merit: 1005
December 01, 2012, 12:13:00 AM
#25
PoS as implemented is by block number, not time, hence time attacks do not affect it.  It looks like you can just mine a PoS transaction at 1 diff after a certain number of blocks have passed

What I don't understand so much is how they're signed for securely
legendary
Activity: 1050
Merit: 1003
November 30, 2012, 11:26:44 PM
#24
This is a misunderstanding. Proof-of-work blocks do not act as clock for proof-of-stake blocks. Proof-of-stake blocks have their own difficulty and will adjust toward target spacing of 10 minutes all by their own.

Is the PoW clock just ignored then for blockchain validity purposes? If so, good idea. That means you can toss PoW entirely.

What do you do about PoS miners who report blockchains from the future? A lot of coin-age can be destroyed if we allow 2025 to reported as occurring tomorrow, even if only a tiny % of coins did the mining.

 
hero member
Activity: 686
Merit: 500
Wat
November 30, 2012, 06:21:51 PM
#23
The real security problem with ppcoin ?  The possibility that Sunny King = realSolid
legendary
Activity: 1205
Merit: 1010
November 30, 2012, 04:36:57 PM
#22

I've been thinking about this a lot lately. The point of PoW in PPC is as a Clock, and an initial coin minter. Without PoW PPC does not know when 10 minutes has elapsed. Checkpoints are there to ensure that nobody does a double-spend before the network gets big enough to defend itself, as well as provide a clock of last resort (IIRC) I believe that sunny-king did mention removing check-pointing in and upcoming build. (note, I'm not reading the code, using my memory of descriptions)

The Clock aspect of PoW makes it almost impossible to move away from, or at least replace. Using an external timeserver would open up all sorts of network vulnerabilities and attacks, same with checkpoint servers, or any other centralized mechanism.


This is a misunderstanding. Proof-of-work blocks do not act as clock for proof-of-stake blocks. Proof-of-stake blocks have their own difficulty and will adjust toward target spacing of 10 minutes all by their own.
sr. member
Activity: 350
Merit: 250
November 30, 2012, 04:26:45 PM
#21
Replying from mobile, so no elaborate quoting.

On timing, I think it is true that you do not care about absolute time, you do care about approximate time of intervals however. With the transaction based timing mechanism you described I believe that block time would get shorter and shorter as the network was busier, not a steady flow. Setting and trying to maintain a regular block interval is important, IMHO, and should be a mechanism that is as far from being manipulated as possible.

I also think asking or encouraging folks to keep their coins online is a mistake. Even if everyone tries, there will be plenty of times that less than 50% of coins are offline, especially if folks want to protect them. So if the currency is successful the majority will disappear.

Simpler is better, this last suggestion is so complex it will take a lot of testing to check every possible angle.
legendary
Activity: 1050
Merit: 1003
November 30, 2012, 01:15:40 PM
#20
Looking at the code, it seems to basically be solidcoin without maybe only one master-node instead of several master-nodes.

I tried to hack out all proof of work and all checking of checkpoints but then both sides seem to wait for the other to start saying something.

OK, I have finished the thread, that took a while (because there is a lot of info here, thanks guys)

I've been thinking about this a lot lately. The point of PoW in PPC is as a Clock, and an initial coin minter. Without PoW PPC does not know when 10 minutes has elapsed. Checkpoints are there to ensure that nobody does a double-spend before the network gets big enough to defend itself, as well as provide a clock of last resort (IIRC) I believe that sunny-king did mention removing check-pointing in and upcoming build. (note, I'm not reading the code, using my memory of descriptions)

The Clock aspect of PoW makes it almost impossible to move away from, or at least replace. Using an external timeserver would open up all sorts of network vulnerabilities and attacks, same with checkpoint servers, or any other centralized mechanism.

Time itself is unimportant. The only reason the time is necessary is to allow you to iterate over something. I have been thinking about txn counts as an alternative clock.

Think about the following (ignore the double-spending issues for now, just think about txn-based time):

1) Txns have a block stamp t (i.e. rather than being independent of the blockchain, they must go in certain block t and cannot go in t-1 or t+1)
2) Certain types of txns can iterate the clock. Suppose that only txns that move 210 or more BTC iterate the clock.  [I just use BTC because it's familiar]
3) Suppose further that every txn requires a minimum fee, c>0. This means that each iteration has a minimum cost of c.
4) For for any block t, there are up to 100,000 possible clock iterations.  [because at most 21 million BTC could be moved in 100,000 210 BTC txns in a single block]
Iteration txns (anything with > 210 BTC inputs) are escrowed for 6 blocks before they can be spent again.
5) With any given iteration there is a chance of moving to block t+1 and starting the process over again.
6) Now for this to work as a timing mechanism we have to be sure that the probability that the clock runs out is 0. Suppose for example that the probability of success at any iteration is 0.01. In 10,000 iterations, this gives us a probability of 10,000 consecutive failures of 3*10^-44. This is effectively 0. Probably humanity is dead before the clock stops.
Note: A block includes not just the iterated txns, but also any number of smaller, fee-paying txns as well.

If we introduce this type of 'clock' into PPCoin it creates problems. Given a sizable stash of coin divided among different addresses, I can merrily mine blocks without any connection to the network, potentially overtaking the main chain with some probability. However, I think this problem can be overcome.

Think about the following process for block generation (similar to what I describe in the proof of stake wiki):
1) Any pair (block number, n) maps to a random lottery draw.  (where n is the number of iterations)
2) The lottery picks 4 random satoshis in sequence. The satoshi owners are invited to sign a block including any n iterated txns.
3) Let's say the 4 owners communicate via the network and agree to mine a block.
4) The first lottery winner creates a block and hashes it. Then he broadcasts the hash though the network. The other winners sign the hash and rebroadcast it.
5) Now the first lottery winner broadcasts the block together with the signed hash. [Note that one person can't modify the block because then the signature hash won't match. A conspiracy of these 4 guys could collude to generate many different blocks of the same height. One solution is to a) just pick one if we hear that someone has built on it. b) If not, combine any two duplicate blocks that build on the same root into a 'null block', ignore all txns in the 'null block', and build on the null block.]

If we have a process like this, it is hard to mine offline. Suppose for example suppose I have 10% of all coins. To mine a block offline I need to draw my own satoshis 4 times in a row. This takes about 10,000 iterations on average. With 10% of all coins, I can do up to 10,0000 iterations, so it within my capability. However, if I want to mine 6 blocks in a row it becomes much harder because of the escrow of txns. The problem is more complicated, but I'll just pretend I do this by dividing coins into 6 even piles. With 6 even piles, I can only do 1,667 iterations per block. This gives me a success probability of about 8% per block. For 6 blocks in a row that is about once every 4 million blocks.
 
On the other hand, consider legit public mining. Suppose 50% of coins are online. Then the success probability from one iteration is 6.25%. We would get a block about once every 16 iterations. That is very good.  If only 20% of coins are online, however, then we only get a block about once every 625 iterations. People could stir the pot if necessary by sending out some txns; perhaps that could be automated in the client. That should still be fine. If we drop lower than this, the iterations are going to blow up and double-spending risk becomes a problem. Thus, there needs to be strong incentives to keep coins online.

The incentives should be flexible. If 50% of coins are online then everything is hunky dory and the people who keep their coins offline do not need to be penalized.
One possibility is to issue a block reward that explodes in the number of iterations (e.g. the block reward = k*(average of iterations in recent blocks)^2). Iterations only explode if everyone is offline. Thus if we see lots of iterations, then we need to drastically increase incentives. Drastically increasing block reward redistributes wealth to whoever is online, solving the problem of offline coins (or lost coins). When hyperinflation is necessary, people who can't manage nodes would store their coins in an online bank that pays interest. The bank funds this by putting the coins online and earning interest.

Another interesting thing about this is it could be scheduled. Recall that the lottery draws are deterministic. If we approximately know who is online, then we can approximately know who will mine the upcoming blocks. This might allow for a rapid communication process and relatively quick blocks.

Anyways, there are kinks to be ironed out. The parameters need adjusting.

Are there some crippling issues that I am missing? I think that secret mining attacks are mostly covered here. Are there public attacks that will work?
legendary
Activity: 1022
Merit: 1033
November 30, 2012, 10:44:44 AM
#19
1) PPCoin developers are really awful at English Communication.
2) PPCoin developers are really awful at PR.
3) PPCoin developers are jealous and afraid of being copied/outdone. (open source, but not open interpretation of source)
4) PPCoin developers know something bad and they are hiding it strategically.

x) They know that design is half-assed, but it sort of works for now so they just don't care, hoping that in future problem will be solved.

Sunny King has no problems with communications and PR, he simply does not give a fuck.

sr. member
Activity: 350
Merit: 250
November 30, 2012, 10:17:15 AM
#18
Looking at the code, it seems to basically be solidcoin without maybe only one master-node instead of several master-nodes.

I tried to hack out all proof of work and all checking of checkpoints but then both sides seem to wait for the other to start saying something.

OK, I have finished the thread, that took a while (because there is a lot of info here, thanks guys)

I've been thinking about this a lot lately. The point of PoW in PPC is as a Clock, and an initial coin minter. Without PoW PPC does not know when 10 minutes has elapsed. Checkpoints are there to ensure that nobody does a double-spend before the network gets big enough to defend itself, as well as provide a clock of last resort (IIRC) I believe that sunny-king did mention removing check-pointing in and upcoming build. (note, I'm not reading the code, using my memory of descriptions)

The Clock aspect of PoW makes it almost impossible to move away from, or at least replace. Using an external timeserver would open up all sorts of network vulnerabilities and attacks, same with checkpoint servers, or any other centralized mechanism.

Why not do merged mining with BitCoin for the PoW, so you can keep the fork "pure" and focused on the Proof of Stake aspects?

Initial coin minting needs to be heavily deflationary too, in my opinion, that is another issue I have with PPC. No matter how far I game it out, it keeps looking like mining will ALWAYS be FAR more lucrative than holding, and since transaction fees are destroyed there is no incentive to mine PoW if we tried to just make the existing coin deflationary.

I guess my wish list is:
- Deflationary initial coin distribution that is...
- ... replaced by transaction fees over time, but... 
- ... no hard limit on coins since PoS will continue forever.
- Merged Mining with BitCoin for PoW
- Clear direction with milestones,
- and much better communication.

Cabin made a good point on transaction fees, without fees being shared with miners, how do we incent folks to include some transactions over others. We have to assume that we might get more transactions than we can handle at some point and will need to prioritize...

ADDENDUM: While doing a revision of the POS reward scheme, it may also be beneficial to investigate how the incentive structure for including transactions can be optimized. Maybe the block reward can be proportional to the overall destroyed coinage instead of the stake transaction (however, the "likelihood" of generating the POS block should only be determined by the stake transaction)
Then including transactions which destroy significant coinage is beneficial to the miner and it's an incentive to include them instead of leaving them out. The above mentioned exponential modifier can work on the total coinage for the block instead of the stake transaction...

I Disagree with this one, the only benefit from "stake" should be during holding. If you elect to stop holding and move it, it's income stops.
I like the declining aggregate interest idea, but it would just cause folks to move coins around on a quarterly or annual basis to optimize rewards. This would however essentially require that ALL coins be stored online, or come online regularly, so it might be an overall security disadvantage. I think if you bring your wallet back online after 1 year, you should be able to get 1 year of interest, unless this is Proof of Online Stake (POS?) in which case we should actually be checking availability of coins or something. (I think)

donator
Activity: 994
Merit: 1000
November 29, 2012, 02:30:53 AM
#17
d) Find an incentive mechanism that detects and penalizes people who do (2) regardless of whether they succeed or fail. I think the solution here is to make mining "quasi- deterministic". Deterministic mining means there is only one true history. Quasi-deterministic mining means that there is a limited set of alternative histories. I'm working on a pure proof-of-stake system that does a combination of (c) and (d). The system would use txns to measure "time". I will post a brainstorming thread about it shortly. I would appreciate comments. [Much too big of a change to be a fork of PPC.]
as usual good points. I agree - having an incentive for including transactions suddenly provides incentives for block history rewriting. The only way to compensate for that is to penalize events which indicate rewriting attempts. We don't have a solution for that yet. So I am looking forward to your brainstorming thread.
legendary
Activity: 1050
Merit: 1003
November 29, 2012, 02:12:12 AM
#16
good catch. Indeed - generating more blocks from the same stake will give you more bang for the buck, if you can claim a percentage of the destroyed coinage from the included transactions as a reward. Thus you have an incentive to have your stake existing as many small outputs. An indirect consequence of that is that you need to have more uptime for the stake - because smaller stakes have larger sampling requirements, i.e. need more online time.

That incentive may be so strong, that you may not have to modify the 1% per year reward at all to encourage POS miner to be online.
E.g take block 16924:
Coin-days Destroyed: 47119.621896
POS Generation: 0.43 Total (503.69*0.01*31/365)
Stake: 503.69

If you allow for 1%/365= transaction reward per block you have an additional reward of
47119.621896*0.000027=1.27

which is almost three times the actual POS reward. That means that a POS miner which is always online may easily effectively generate 3-4% on the used stake, which suddenly makes it much more attractive...

There is some incentive to include txns in PPCoin already. There is a fee that destroys currency. As a stake holder, the more currency destroyed the better off I am. It is a weak incentive, but it may be sufficient. You don't need to pay people to do something extremely easy. In fact, you certainly shouldn't pay them a lot. You would be wasting money.

That thread contains a good discussion. It points out some problems with txn fees in a PoS context. I go over some issues below.

Incentives in PPCoin are based on interest and not txn fees for good reason.

There is a fundamental problem with history revision in a PoS system. As a miner I have at least two choices:
1) Mine on the main chain using the official client
2) Mine on the main chain and older chains using a modified client.  (naughty naughty)

The number of blocks I mine per unit time is random. If I do (2), I can explore alternate histories where I mine a larger share of blocks.  If a large fraction f of users do (2), then there will be periodic reorganization events. If f=100%, then there will be perpetual reorganization and no "main chain."

In PPCoin, doing (2) is not rational. There is an infinitesimal personal benefit related to more frequent compounding of interest. There is also a cost because doing (2) undermines PPCoins market value. If you have a non-negligible stake, then you care about market valuation and it will never be rational to do (2). If you have a negligible stake, then what you do doesn't really matter anyway.

If we introduce piece rates for txns, the calculation changes. The benefits of more frequent mining become non-trivial. This encourages more people to do (2). I expect that for any txn fee, r, there will be a cut-off stake level, s*(r), where for s
What can be done about this?
a) Restrict yourself to weak incentives (e.g. interest perhaps with a time cap to weakly motivate more frequent mining) [This is what I am suggesting for PPCoin.]
b) Mitigate the theft risks of keeping coins online. This is probably a large reason why people would avoid mining. [Again I am suggesting this for PPCoin.]
c) Introduce a mechanism that makes it extremely unlikely that minority chains overtake the main chain. Essentially this means that (2) is irrelevant unless the fraction of people doing (2) approaches 100%. [I describe a mechanism like this here: https://en.bitcoin.it/wiki/Proof_of_Stake. It is too big of a change to be a fork of PPC]
d) Find an incentive mechanism that detects and penalizes people who do (2) regardless of whether they succeed or fail. I think the solution here is to make mining "quasi- deterministic". Deterministic mining means there is only one true history. Quasi-deterministic mining means that there is a very limited set of alternative histories and you have to pay everyone coins to search through them. I'm working on a pure proof-of-stake system that does a combination of (c) and (d). The system would use txns to measure "time". I will post a brainstorming thread about it shortly. I would appreciate comments. [Much too big of a change to be a fork of PPC.]
donator
Activity: 994
Merit: 1000
November 29, 2012, 12:38:55 AM
#15
It might also solve several problems at once. I think it would:
- make the number of stakes you claim more important than the amount of the stake transactions (atleast once there are lots of transactions flying around and the destroyed coinage is mostly coming from the included transactions and not the stake transaction itself). This would then encourage you to keep the client running to claim as many stakes as possible and to include as many transactions as possible.
good catch. Indeed - generating more blocks from the same stake will give you more bang for the buck, if you can claim a percentage of the destroyed coinage from the included transactions as a reward. Thus you have an incentive to have your stake existing as many small outputs. An indirect consequence of that is that you need to have more uptime for the stake - because smaller stakes have larger sampling requirements, i.e. need more online time.

That incentive may be so strong, that you may not have to modify the 1% per year reward at all to encourage POS miner to be online.
E.g take block 16924:
Coin-days Destroyed: 47119.621896
POS Generation: 0.43 Total (503.69*0.01*31/365)
Stake: 503.69

If you allow for 1%/365=0.000027 transaction reward per block you have an additional reward of
47119.621896*0.000027=1.27

which is almost three times the actual POS reward. That means that a POS miner which is always online may easily effectively generate 3-4% on the used stake, which suddenly makes it much more attractive...

A change in the POS incentive structure like above would also likely put the discussion about the lack of transaction fees to rest:
https://bitcointalksearch.org/topic/ppcoin-transaction-fees-114664
sr. member
Activity: 604
Merit: 250
November 28, 2012, 10:15:39 PM
#14

3) Cap interest accumulation at 90 days. [Incentive to keep node running is much too weak right now.]
I thought that this is implemented:
Quote
CBigNum bnCoinDay = CBigNum(nValueIn) * min(txNew.nTime-pcoin.first->nTime, (unsigned int)STAKE_MAX_AGE) / COIN / (24 * 60 * 60);
STAKE_MAX_AGE is set to 90 days.
This is for coin generation. Not for the amount of interest you are paid. PoS security is directly proportional to the amount of coin-age currently on line.
If I don't earn more by being online often, then there is no incentive to come online. Instead I can just hold my PPCoin until I want to cash out.
Then quickly generate my stake block and sell. This does do much to secure the network at all. I should be generating many stake blocks to earn my interest, not just one.
The cap ensures that I have to come online at least once in a while.
I see what you mean:
The likelihood of generating a POS block is capped to 90 days. But when it generates a valid POS block it takes the full coinage for calculating the reward, which can be anything above 30 days.

I generally agree that we need an incentive for people to keep their POS generating nodes running. I don't know whether capping the POS reward to 90 days is enough though, since, as you previously pointed out, 1% per year is a minuscule incentive. The only other way to tune that would be to make it attractive to generate a POS block shortly after the 30 day mark. However, you don't want to penalize late redemption of coinage. Usually exponential functions are good for that: E.g make it 4% for 30 days, 2% for 60 days, 1% for 120 days, 0.5% for 240 days. This has the effect that the POS blocks to get a reward which is rather constant instead of being proportional to the coinage. However, quick redemption (after 30 days) allows a quicker turnover and is beneficial to the POS miner. However this is a drastic change - and the quickest way to get this sorted out would be to work together with Sunny on the issue.

ADDENDUM: While doing a revision of the POS reward scheme, it may also be beneficial to investigate how the incentive structure for including transactions can be optimized. Maybe the block reward can be proportional to the overall destroyed coinage instead of the stake transaction (however, the "likelihood" of generating the POS block should only be determined by the stake transaction)
Then including transactions which destroy significant coinage is beneficial to the miner and it's an incentive to include them instead of leaving them out. The above mentioned exponential modifier can work on the total coinage for the block instead of the stake transaction...


This part in interesting:

Maybe the block reward can be proportional to the overall destroyed coinage instead of the stake transaction (however, the "likelihood" of generating the POS block should only be determined by the stake transaction)

It might also solve several problems at once. I think it would:
- make the number of stakes you claim more important than the amount of the stake transactions (atleast once there are lots of transactions flying around and the destroyed coinage is mostly coming from the included transactions and not the stake transaction itself). This would then encourage you to keep the client running to claim as many stakes as possible and to include as many transactions as possible.
- there would be more reason to include large transactions and not so much of a reason to include the spammy small ones. This is a bit weak but it might give larger transactions higher priority which isn't so bad.

The percents would have to be tweaked and probably dynamic somehow though, otherwise the rewards and inflation would get out of hand as the network processed more and more transactions.
donator
Activity: 994
Merit: 1000
November 27, 2012, 04:49:40 PM
#13

3) Cap interest accumulation at 90 days. [Incentive to keep node running is much too weak right now.]
I thought that this is implemented:
Quote
CBigNum bnCoinDay = CBigNum(nValueIn) * min(txNew.nTime-pcoin.first->nTime, (unsigned int)STAKE_MAX_AGE) / COIN / (24 * 60 * 60);
STAKE_MAX_AGE is set to 90 days.
This is for coin generation. Not for the amount of interest you are paid. PoS security is directly proportional to the amount of coin-age currently on line.
If I don't earn more by being online often, then there is no incentive to come online. Instead I can just hold my PPCoin until I want to cash out.
Then quickly generate my stake block and sell. This does do much to secure the network at all. I should be generating many stake blocks to earn my interest, not just one.
The cap ensures that I have to come online at least once in a while.
I see what you mean:
The likelihood of generating a POS block is capped to 90 days. But when it generates a valid POS block it takes the full coinage for calculating the reward, which can be anything above 30 days.

I generally agree that we need an incentive for people to keep their POS generating nodes running. I don't know whether capping the POS reward to 90 days is enough though, since, as you previously pointed out, 1% per year is a minuscule incentive. The only other way to tune that would be to make it attractive to generate a POS block shortly after the 30 day mark. However, you don't want to penalize late redemption of coinage. Usually exponential functions are good for that: E.g make it 4% for 30 days, 2% for 60 days, 1% for 120 days, 0.5% for 240 days. This has the effect that the POS blocks to get a reward which is rather constant instead of being proportional to the coinage. However, quick redemption (after 30 days) allows a quicker turnover and is beneficial to the POS miner. However this is a drastic change - and the quickest way to get this sorted out would be to work together with Sunny on the issue.

ADDENDUM: While doing a revision of the POS reward scheme, it may also be beneficial to investigate how the incentive structure for including transactions can be optimized. Maybe the block reward can be proportional to the overall destroyed coinage instead of the stake transaction (however, the "likelihood" of generating the POS block should only be determined by the stake transaction)
Then including transactions which destroy significant coinage is beneficial to the miner and it's an incentive to include them instead of leaving them out. The above mentioned exponential modifier can work on the total coinage for the block instead of the stake transaction...
legendary
Activity: 2940
Merit: 1090
November 27, 2012, 03:12:38 PM
#12
Here is my hacks-so-far, as diff against github default main/head/whatever:

Code:
diff ../poscoin/src/checkpoints.cpp src/checkpoints.cpp
33c33
<         return true; // poscoin has no checkpoints
---
>         if (fTestNet) return true; // Testnet has no checkpoints
88d87
<         return true;
Only in ../poscoin/src: checkpoints.ppc
Common subdirectories: ../poscoin/src/json and src/json
diff ../poscoin/src/main.cpp src/main.cpp
866c866
<     return min(MIN_STAKE_REWARD,nSubsidy);
---
>     return nSubsidy;
1774c1774
<         bnCentSecond += CBigNum(nValueIn) * max(nTime-txPrev.nTime,COIN_AGE_CAP) / CENT;
---
>         bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT;
1875,1876c1875,1876
<     if ((nBestHeight > 10) && IsProofOfWork())
<         return DoS(50, error("CheckBlock() : proof of work is fail! :)"));
---
>     if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits))
>         return DoS(50, error("CheckBlock() : proof of work failed"));
1979,1980c1979,1980
< //    if (!Checkpoints::CheckHardened(nHeight, hash))
< //        return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight));
---
>     if (!Checkpoints::CheckHardened(nHeight, hash))
>         return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight));
1983,1984c1983,1984
< //    if (!Checkpoints::CheckSync(hash, pindexPrev))
< //        return error("AcceptBlock() : rejected by synchronized checkpoint");
---
>     if (!Checkpoints::CheckSync(hash, pindexPrev))
>         return error("AcceptBlock() : rejected by synchronized checkpoint");
3601,3603d3600
<     if (nBestHeight < FIRST_STAKE_BLOCK)
< fProofOfStake = 0;
<
diff ../poscoin/src/main.h src/main.h
35,37d34
< static const unsigned int COIN_AGE_CAP = 90 * 24 * 60 * 60;
< static const unsigned int FIRST_STAKE_BLOCK = 10;
< static const int64 MIN_STAKE_REWARD = COIN;
111c108
< CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake=true);
---
> CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake=false);
Common subdirectories: ../poscoin/src/obj and src/obj
Common subdirectories: ../poscoin/src/obj-test and src/obj-test
diff ../poscoin/src/protocol.cpp src/protocol.cpp
25,31c25,26
< //static unsigned char pchMessageStartBitcoin[4] = { 0xf9, 0xbe, 0xb4, 0xd9 };
< //static unsigned char pchMessageStartPPCoin[4] = { 0xe6, 0xe8, 0xe9, 0xe5 };
< //static unsigned int nMessageStartSwitchTime = 1347300000;
<
< // POSoin message start
< static unsigned char pchMessageStartBitcoin[4] = { 'P', 'O', 'S', ':' };
< static unsigned char pchMessageStartPPCoin[4] = { 'P', 'O', 'S', ':' };
---
> static unsigned char pchMessageStartBitcoin[4] = { 0xf9, 0xbe, 0xb4, 0xd9 };
> static unsigned char pchMessageStartPPCoin[4] = { 0xe6, 0xe8, 0xe9, 0xe5 };
Common subdirectories: ../poscoin/src/qt and src/qt
Common subdirectories: ../poscoin/src/test and src/test


(I called it POScoin. Whether it will end up any use for Point Of Sale systems remains to be seen though. Smiley)

-MarkM-
legendary
Activity: 1484
Merit: 1005
November 27, 2012, 02:12:53 PM
#11
Relevant code changes to main.cpp

Code:
// ppcoin: miner's coin stake is rewarded based on coin age spent (coin-days)
int64 GetProofOfStakeReward(int64 nCoinAge)
{
    static int64 nRewardCoinYear = CENT;  // creation amount per coin-year
    int64 nSubsidy = nCoinAge * 33 / (365 * 33 + 8) * nRewardCoinYear;
    if (fDebug && GetBoolArg("-printcreation"))
        printf("GetProofOfStakeReward(): create=%s nCoinAge=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nCoinAge);
    return nSubsidy;
}

static const int64 nTargetTimespan = 7 * 24 * 60 * 60;  // one week
static const int64 nTargetSpacingStake = 10 * 60;       // ten minutes
static const int64 nTargetSpacingWorkMax = 2 * 60 * 60; // two hours

Code:
unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake)
{
    if (pindexLast == NULL)
        return bnProofOfWorkLimit.GetCompact(); // genesis block

    const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
    if (pindexPrev->pprev == NULL)
        return bnInitialHashTarget.GetCompact(); // first block
    const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
    if (pindexPrevPrev->pprev == NULL)
        return bnInitialHashTarget.GetCompact(); // second block

    int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime();

    // ppcoin: target change every block
    // ppcoin: retarget with exponential moving toward target spacing
    CBigNum bnNew;
    bnNew.SetCompact(pindexPrev->nBits);
    int64 nTargetSpacing = fProofOfStake? nTargetSpacingStake : min(nTargetSpacingWorkMax, nTargetSpacingStake * (1 + pindexLast->nHeight - pindexPrev->nHeight));
    int64 nInterval = nTargetTimespan / nTargetSpacing;
    bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing);
    bnNew /= ((nInterval + 1) * nTargetSpacing);

    if (bnNew > bnProofOfWorkLimit)
        bnNew = bnProofOfWorkLimit;

    return bnNew.GetCompact();
}

Code:
bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs,
                                 map& mapTestPool, const CDiskTxPos& posThisTx,
                                 const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash)
{
         ...
        if (IsCoinStake())
        {
            // ppcoin: coin stake tx earns reward instead of paying fee
            uint64 nCoinAge;
            if (!GetCoinAge(txdb, nCoinAge))
                return error("ConnectInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str());
            int64 nStakeReward = GetValueOut() - nValueIn;
            if (nStakeReward > GetProofOfStakeReward(nCoinAge) - GetMinFee() + MIN_TX_FEE)
                return DoS(100, error("ConnectInputs() : %s stake reward exceeded", GetHash().ToString().substr(0,10).c_str()));
        }
        else
        {
            if (nValueIn < GetValueOut())
                return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()));

            // Tally transaction fees
            int64 nTxFee = nValueIn - GetValueOut();
            if (nTxFee < 0)
                return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
            // ppcoin: enforce transaction fees for every block
            if (nTxFee < GetMinFee())
                return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(GetMinFee()).c_str(), FormatMoney(nTxFee).c_str())) : false;
            nFees += nTxFee;
            if (!MoneyRange(nFees))
                return DoS(100, error("ConnectInputs() : nFees out of range"));
        }
    }

    return true;
}

Code:
// ppcoin: coinstake must meet hash target according to the protocol:
// kernel (input 0) must meet the formula
//     hash(nBits + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDay
// this ensures that the chance of getting a coinstake is proportional to the
// amount of coin age one owns.
// The reason this hash is chosen is the following:
//   nBits: encodes all past block timestamps, making computing hash in advance
//          more difficult
//   txPrev.block.nTime: prevent nodes from guessing a good timestamp to
//                       generate transaction for future advantage
//   txPrev.offset: offset of txPrev inside block, to reduce the chance of
//                  nodes generating coinstake at the same time
//   txPrev.nTime: reduce the chance of nodes generating coinstake at the same
//                 time
//   txPrev.vout.n: output number of txPrev, to reduce the chance of nodes
//                  generating coinstake at the same time
//   block/tx hash should not be used here as they can be generated in vast
//   quantities so as to generate blocks faster, degrading the system back into
//   a proof-of-work situation.
//
bool CTransaction::CheckProofOfStake(unsigned int nBits) const
{
    CBigNum bnTargetPerCoinDay;
    bnTargetPerCoinDay.SetCompact(nBits);
 
    if (!IsCoinStake())
        return true;

    // Kernel (input 0) must match the stake hash target per coin age (nBits)
    const CTxIn& txin = vin[0];

    // First try finding the previous transaction in database
    CTxDB txdb("r");
    CTransaction txPrev;
    CTxIndex txindex;
    if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
        return false;  // previous transaction not in main chain
    txdb.Close();
    if (nTime < txPrev.nTime)
        return false;  // Transaction timestamp violation

    // Verify signature
    if (!VerifySignature(txPrev, *this, 0, true, 0))
        return DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", GetHash().ToString().c_str()));

    // Read block header
    CBlock block;
    if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
        return false; // unable to read block of previous transaction
    if (block.GetBlockTime() + nStakeMinAge > nTime)
        return false; // only count coins meeting min age requirement

    int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
    CBigNum bnCoinDay = CBigNum(nValueIn) * min(nTime-txPrev.nTime, (unsigned int)STAKE_MAX_AGE) / COIN / (24 * 60 * 60);
    // Calculate hash
    CDataStream ss(SER_GETHASH, 0);
    ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << txPrev.nTime << txin.prevout.n << nTime;
    if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay)
        return true;
    else
        return DoS(100, error("CheckProofOfStake() : check target failed on coinstake %s", GetHash().ToString().c_str()));
}

// ppcoin: total coin age spent in transaction, in the unit of coin-days.
// Only those coins meeting minimum age requirement counts. As those
// transactions not in main chain are not currently indexed so we
// might not find out about their coin age. Older transactions are
// guaranteed to be in main chain by sync-checkpoint. This rule is
// introduced to help nodes establish a consistent view of the coin
// age (trust score) of competing branches.
bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const
{
    CBigNum bnCentSecond = 0;  // coin age in the unit of cent-seconds
    nCoinAge = 0;

    if (IsCoinBase())
        return true;

    BOOST_FOREACH(const CTxIn& txin, vin)
    {
        // First try finding the previous transaction in database
        CTransaction txPrev;
        CTxIndex txindex;
        if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
            continue;  // previous transaction not in main chain
        if (nTime < txPrev.nTime)
            return false;  // Transaction timestamp violation

        // Read block header
        CBlock block;
        if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
            return false; // unable to read block of previous transaction
        if (block.GetBlockTime() + nStakeMinAge > nTime)
            continue; // only count coins meeting min age requirement

        int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
        bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT;

        if (fDebug && GetBoolArg("-printcoinage"))
            printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str());
    }

    CBigNum bnCoinDay = bnCentSecond * CENT / COIN / (24 * 60 * 60);
    if (fDebug && GetBoolArg("-printcoinage"))
        printf("coin age bnCoinDay=%s\n", bnCoinDay.ToString().c_str());
    nCoinAge = bnCoinDay.getuint64();
    return true;
}

// ppcoin: total coin age spent in block, in the unit of coin-days.
bool CBlock::GetCoinAge(uint64& nCoinAge) const
{
    nCoinAge = 0;

    CTxDB txdb("r");
    BOOST_FOREACH(const CTransaction& tx, vtx)
    {
        uint64 nTxCoinAge;
        if (tx.GetCoinAge(txdb, nTxCoinAge))
            nCoinAge += nTxCoinAge;
        else
            return false;
    }

    if (nCoinAge == 0) // block coin age minimum 1 coin-day
        nCoinAge = 1;
    if (fDebug && GetBoolArg("-printcoinage"))
        printf("block coin age total nCoinDays=%"PRI64d"\n", nCoinAge);
    return true;
}


bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos)
{
    // Check for duplicate
    uint256 hash = GetHash();
    if (mapBlockIndex.count(hash))
        return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,20).c_str());

    // Construct new block index object
    CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this);
    if (!pindexNew)
        return error("AddToBlockIndex() : new CBlockIndex failed");
    map::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
    if (pindexNew->fProofOfStake)
        setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));

    pindexNew->phashBlock = &((*mi).first);
    map::iterator miPrev = mapBlockIndex.find(hashPrevBlock);
    if (miPrev != mapBlockIndex.end())
    {
        pindexNew->pprev = (*miPrev).second;
        pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
    }

    // ppcoin: compute chain trust score
    pindexNew->bnChainTrust = (pindexNew->pprev ? pindexNew->pprev->bnChainTrust : 0) + pindexNew->GetBlockTrust();

    CTxDB txdb;
    if (!txdb.TxnBegin())
        return false;
    txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
    if (!txdb.TxnCommit())
        return false;

    // New best
    if (pindexNew->bnChainTrust > bnBestChainTrust)
        if (!SetBestChain(txdb, pindexNew))
            return false;

    txdb.Close();

    if (pindexNew == pindexBest)
    {
        // Notify UI to display prev block's coinbase if it was ours
        static uint256 hashPrevBestCoinBase;
        UpdatedTransaction(hashPrevBestCoinBase);
        hashPrevBestCoinBase = vtx[0].GetHash();
    }

    MainFrameRepaint();
    return true;
}




bool CBlock::CheckBlock() const
{
    // These are checks that are independent of context
    // that can be verified before saving an orphan block.

    // Size limits
    if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
        return DoS(100, error("CheckBlock() : size limits failed"));

    // Check proof of work matches claimed amount
    if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits))
        return DoS(50, error("CheckBlock() : proof of work failed"));

    // Check timestamp
    if (GetBlockTime() > GetAdjustedTime() + nMaxClockDrift)
        return error("CheckBlock() : block timestamp too far in the future");

    // First transaction must be coinbase, the rest must not be
    if (vtx.empty() || !vtx[0].IsCoinBase())
        return DoS(100, error("CheckBlock() : first tx is not coinbase"));
    for (unsigned int i = 1; i < vtx.size(); i++)
        if (vtx[i].IsCoinBase())
            return DoS(100, error("CheckBlock() : more than one coinbase"));

    // ppcoin: only the second transaction can be the optional coinstake
    for (int i = 2; i < vtx.size(); i++)
        if (vtx[i].IsCoinStake())
            return DoS(100, error("CheckBlock() : coinstake in wrong position"));

    // ppcoin: coinbase output should be empty if proof-of-stake block
    if (IsProofOfStake() && (vtx[0].vout.size() != 1 || !vtx[0].vout[0].IsEmpty()))
        return error("CheckBlock() : coinbase output not empty for proof-of-stake block");

    // Check coinbase timestamp
    if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift)
        return DoS(50, error("CheckBlock() : coinbase timestamp is too early"));

    // Check coinstake timestamp
    if (IsProofOfStake() && GetBlockTime() > (int64)vtx[1].nTime + nMaxClockDrift)
        return DoS(50, error("CheckBlock() : coinstake timestamp is too early"));

    // Check coinbase reward
    if (vtx[0].GetValueOut() > (IsProofOfWork()? (GetProofOfWorkReward(nBits) - vtx[0].GetMinFee() + MIN_TX_FEE) : 0))
        return DoS(50, error("CheckBlock() : coinbase reward exceeded %s > %s",
                   FormatMoney(vtx[0].GetValueOut()).c_str(),
                   FormatMoney(IsProofOfWork()? GetProofOfWorkReward(nBits) : 0).c_str()));

    // Check transactions
    BOOST_FOREACH(const CTransaction& tx, vtx)
    {
        if (!tx.CheckTransaction())
            return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed"));
        // ppcoin: check transaction timestamp
        if (GetBlockTime() < (int64)tx.nTime)
            return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp"));
    }

    // Check for duplicate txids. This is caught by ConnectInputs(),
    // but catching it earlier avoids a potential DoS attack:
    set uniqueTx;
    BOOST_FOREACH(const CTransaction& tx, vtx)
    {
        uniqueTx.insert(tx.GetHash());
    }
    if (uniqueTx.size() != vtx.size())
        return DoS(100, error("CheckBlock() : duplicate transaction"));

    unsigned int nSigOps = 0;
    BOOST_FOREACH(const CTransaction& tx, vtx)
    {
        nSigOps += tx.GetLegacySigOpCount();
    }
    if (nSigOps > MAX_BLOCK_SIGOPS)
        return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"));

    // Check merkleroot
    if (hashMerkleRoot != BuildMerkleTree())
        return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));

    // ppcoin: check block signature
    if (!CheckBlockSignature())
        return DoS(100, error("CheckBlock() : bad block signature"));

    return true;
}

bool CBlock::AcceptBlock()
{
    // Check for duplicate
    uint256 hash = GetHash();
    if (mapBlockIndex.count(hash))
        return error("AcceptBlock() : block already in mapBlockIndex");

    // Get prev block index
    map::iterator mi = mapBlockIndex.find(hashPrevBlock);
    if (mi == mapBlockIndex.end())
        return DoS(10, error("AcceptBlock() : prev block not found"));
    CBlockIndex* pindexPrev = (*mi).second;
    int nHeight = pindexPrev->nHeight+1;

    // Check proof-of-work or proof-of-stake
    if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake()))
        return DoS(100, error("AcceptBlock() : incorrect proof-of-work/proof-of-stake"));

    // Check timestamp against prev
    if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime())
        return error("AcceptBlock() : block's timestamp is too early");

    // Check that all transactions are finalized
    BOOST_FOREACH(const CTransaction& tx, vtx)
        if (!tx.IsFinal(nHeight, GetBlockTime()))
            return DoS(10, error("AcceptBlock() : contains a non-final transaction"));

    // Check that the block chain matches the known block chain up to a hardened checkpoint
    if (!Checkpoints::CheckHardened(nHeight, hash))
        return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight));

    // ppcoin: check that the block satisfies synchronized checkpoint
    if (!Checkpoints::CheckSync(hash, pindexPrev))
        return error("AcceptBlock() : rejected by synchronized checkpoint");

    // Write block to history file
    if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK, CLIENT_VERSION)))
        return error("AcceptBlock() : out of disk space");
    unsigned int nFile = -1;
    unsigned int nBlockPos = 0;
    if (!WriteToDisk(nFile, nBlockPos))
        return error("AcceptBlock() : WriteToDisk failed");
    if (!AddToBlockIndex(nFile, nBlockPos))
        return error("AcceptBlock() : AddToBlockIndex failed");

    // Relay inventory, but don't relay old inventory during initial block download
    int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate();
    if (hashBestChain == hash)
    {
        LOCK(cs_vNodes);
        BOOST_FOREACH(CNode* pnode, vNodes)
            if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
                pnode->PushInventory(CInv(MSG_BLOCK, hash));
    }

    // ppcoin: check pending sync-checkpoint
    Checkpoints::AcceptPendingSyncCheckpoint();

    return true;
}

bool ProcessBlock(CNode* pfrom, CBlock* pblock)
{
    // Check for duplicate
    uint256 hash = pblock->GetHash();
    if (mapBlockIndex.count(hash))
        return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,20).c_str());
    if (mapOrphanBlocks.count(hash))
        return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str());

    // ppcoin: check proof-of-stake
    // Limited duplicity on stake: prevents block flood attack
    // Duplicate stake allowed only when there is orphan child block
    if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
        return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());

    // Preliminary checks
    if (!pblock->CheckBlock())
        return error("ProcessBlock() : CheckBlock FAILED");

    // ppcoin: verify hash target and signature of coinstake tx
    if (pblock->IsProofOfStake() && !pblock->vtx[1].CheckProofOfStake(pblock->nBits))
    {
        printf("WARNING: ProcessBlock(): check proof-of-stake failed for block %s\n", hash.ToString().c_str());
        return false; // do not error here as we expect this during initial block download
    }

    CBlockIndex* pcheckpoint = Checkpoints::GetLastSyncCheckpoint();
    if (pcheckpoint && pblock->hashPrevBlock != hashBestChain && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
    {
        // Extra checks to prevent "fill up memory by spamming with bogus blocks"
        int64 deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime;
        CBigNum bnNewBlock;
        bnNewBlock.SetCompact(pblock->nBits);
        CBigNum bnRequired;
        bnRequired.SetCompact(ComputeMinWork(GetLastBlockIndex(pcheckpoint, pblock->IsProofOfStake())->nBits, deltaTime));

        if (bnNewBlock > bnRequired)
        {
            if (pfrom)
                pfrom->Misbehaving(100);
            return error("ProcessBlock() : block with too little %s", pblock->IsProofOfStake()? "proof-of-stake" : "proof-of-work");
        }
    }

    // ppcoin: ask for pending sync-checkpoint if any
    if (!IsInitialBlockDownload())
        Checkpoints::AskForPendingSyncCheckpoint(pfrom);

    // If don't already have its previous block, shunt it off to holding area until we get it
    if (!mapBlockIndex.count(pblock->hashPrevBlock))
    {
        printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str());
        CBlock* pblock2 = new CBlock(*pblock);
        // ppcoin: check proof-of-stake
        if (pblock2->IsProofOfStake())
        {
            // Limited duplicity on stake: prevents block flood attack
            // Duplicate stake allowed only when there is orphan child block
            if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
                return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str());
            else
                setStakeSeenOrphan.insert(pblock2->GetProofOfStake());
        }
        mapOrphanBlocks.insert(make_pair(hash, pblock2));
        mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2));

        // Ask this guy to fill in what we're missing
        if (pfrom)
        {
            pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2));
            // ppcoin: getblocks may not obtain the ancestor block rejected
            // earlier by duplicate-stake check so we ask for it again directly
            if (!IsInitialBlockDownload())
                pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2)));
        }
        return true;
    }

    // Store to disk
    if (!pblock->AcceptBlock())
        return error("ProcessBlock() : AcceptBlock FAILED");

    // Recursively process any orphan blocks that depended on this one
    vector vWorkQueue;
    vWorkQueue.push_back(hash);
    for (unsigned int i = 0; i < vWorkQueue.size(); i++)
    {
        uint256 hashPrev = vWorkQueue[i];
        for (multimap::iterator mi = mapOrphanBlocksByPrev.lower_bound(hashPrev);
             mi != mapOrphanBlocksByPrev.upper_bound(hashPrev);
             ++mi)
        {
            CBlock* pblockOrphan = (*mi).second;
            if (pblockOrphan->AcceptBlock())
                vWorkQueue.push_back(pblockOrphan->GetHash());
            mapOrphanBlocks.erase(pblockOrphan->GetHash());
            setStakeSeenOrphan.erase(pblockOrphan->GetProofOfStake());
            delete pblockOrphan;
        }
        mapOrphanBlocksByPrev.erase(hashPrev);
    }

    printf("ProcessBlock: ACCEPTED\n");

    // ppcoin: if responsible for sync-checkpoint send it
    if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty())
        Checkpoints::SendSyncCheckpoint(Checkpoints::AutoSelectSyncCheckpoint());

    return true;
}

// ppcoin: sign block
bool CBlock::SignBlock(const CKeyStore& keystore)
{
    vector vSolutions;
    txnouttype whichType;
    const CTxOut& txout = IsProofOfStake()? vtx[1].vout[1] : vtx[0].vout[0];

    if (!Solver(txout.scriptPubKey, whichType, vSolutions))
        return false;
    if (whichType == TX_PUBKEY)
    {
        // Sign
        const valtype& vchPubKey = vSolutions[0];
        CKey key;
        if (!keystore.GetKey(Hash160(vchPubKey), key))
            return false;
        if (key.GetPubKey() != vchPubKey)
            return false;
        return key.Sign(GetHash(), vchBlockSig);
    }
    return false;
}

// ppcoin: check block signature
bool CBlock::CheckBlockSignature() const
{
    if (GetHash() == hashGenesisBlock)
        return vchBlockSig.empty();

    vector vSolutions;
    txnouttype whichType;
    const CTxOut& txout = IsProofOfStake()? vtx[1].vout[1] : vtx[0].vout[0];

    if (!Solver(txout.scriptPubKey, whichType, vSolutions))
        return false;
    if (whichType == TX_PUBKEY)
    {
        const valtype& vchPubKey = vSolutions[0];
        CKey key;
        if (!key.SetPubKey(vchPubKey))
            return false;
        if (vchBlockSig.empty())
            return false;
        return key.Verify(GetHash(), vchBlockSig);
    }
    return false;
}

Code:
// CreateNewBlock:
//   fProofOfStake: try (best effort) to make a proof-of-stake block
CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake)
{
    CReserveKey reservekey(pwallet);

    // Create new block
    auto_ptr pblock(new CBlock());
    if (!pblock.get())
        return NULL;

    // Create coinbase tx
    CTransaction txNew;
    txNew.vin.resize(1);
    txNew.vin[0].prevout.SetNull();
    txNew.vout.resize(1);
    txNew.vout[0].scriptPubKey << reservekey.GetReservedKey() << OP_CHECKSIG;

    // Add our coinbase tx as first transaction
    pblock->vtx.push_back(txNew);

    // ppcoin: if coinstake available add coinstake tx
    static int64 nLastCoinStakeSearchTime = GetAdjustedTime();  // only initialized at startup
    CBlockIndex* pindexPrev = pindexBest;

    if (fProofOfStake)  // attemp to find a coinstake
    {
        pblock->nBits = GetNextTargetRequired(pindexPrev, true);
        CTransaction txCoinStake;
        int64 nSearchTime = GetAdjustedTime();
        if (nSearchTime > nLastCoinStakeSearchTime)
        {
            if (pwallet->CreateCoinStake(*pwallet, pblock->nBits, nSearchTime-nLastCoinStakeSearchTime, txCoinStake))
            {
                pblock->vtx.push_back(txCoinStake);
                pblock->vtx[0].vout[0].SetEmpty();
            }
            nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime;
            nLastCoinStakeSearchTime = nSearchTime;
        }
    }

    pblock->nBits = GetNextTargetRequired(pindexPrev, pblock->IsProofOfStake());

    // Collect memory pool transactions into the block
    int64 nFees = 0;
    {
        LOCK2(cs_main, mempool.cs);
        CTxDB txdb("r");

        // Priority order to process transactions
        list vOrphan; // list memory doesn't move
        map > mapDependers;
        multimap mapPriority;
        for (map::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi)
        {
            CTransaction& tx = (*mi).second;
            if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal())
                continue;

            COrphan* porphan = NULL;
            double dPriority = 0;
            BOOST_FOREACH(const CTxIn& txin, tx.vin)
            {
                // Read prev transaction
                CTransaction txPrev;
                CTxIndex txindex;
                if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
                {
                    // Has to wait for dependencies
                    if (!porphan)
                    {
                        // Use list for automatic deletion
                        vOrphan.push_back(COrphan(&tx));
                        porphan = &vOrphan.back();
                    }
                    mapDependers[txin.prevout.hash].push_back(porphan);
                    porphan->setDependsOn.insert(txin.prevout.hash);
                    continue;
                }
                int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;

                // Read block header
                int nConf = txindex.GetDepthInMainChain();

                dPriority += (double)nValueIn * nConf;

                if (fDebug && GetBoolArg("-printpriority"))
                    printf("priority     nValueIn=%-12"PRI64d" nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority);
            }

            // Priority is sum(valuein * age) / txsize
            dPriority /= ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);

            if (porphan)
                porphan->dPriority = dPriority;
            else
                mapPriority.insert(make_pair(-dPriority, &(*mi).second));

            if (fDebug && GetBoolArg("-printpriority"))
            {
                printf("priority %-20.1f %s\n%s", dPriority, tx.GetHash().ToString().substr(0,10).c_str(), tx.ToString().c_str());
                if (porphan)
                    porphan->print();
                printf("\n");
            }
        }

        // Collect transactions into block
        map mapTestPool;
        uint64 nBlockSize = 1000;
        uint64 nBlockTx = 0;
        int nBlockSigOps = 100;
        while (!mapPriority.empty())
        {
            // Take highest priority transaction off priority queue
            CTransaction& tx = *(*mapPriority.begin()).second;
            mapPriority.erase(mapPriority.begin());

            // Size limits
            unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
            if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN)
                continue;

            // Legacy limits on sigOps:
            unsigned int nTxSigOps = tx.GetLegacySigOpCount();
            if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
                continue;

            // Timestamp limit
            if (tx.nTime > GetAdjustedTime())
                continue;

            // ppcoin: simplify transaction fee - allow free = false
            int64 nMinFee = tx.GetMinFee(nBlockSize, false, GMF_BLOCK);

            // Connecting shouldn't fail due to dependency on other memory pool transactions
            // because we're already processing them in order of dependency
            map mapTestPoolTmp(mapTestPool);
            MapPrevTx mapInputs;
            bool fInvalid;
            if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
                continue;

            int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
            if (nTxFees < nMinFee)
                continue;

            nTxSigOps += tx.GetP2SHSigOpCount(mapInputs);
            if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
                continue;

            if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
                continue;
            mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size());
            swap(mapTestPool, mapTestPoolTmp);

            // Added
            pblock->vtx.push_back(tx);
            nBlockSize += nTxSize;
            ++nBlockTx;
            nBlockSigOps += nTxSigOps;
            nFees += nTxFees;

            // Add transactions that depend on this one to the priority queue
            uint256 hash = tx.GetHash();
            if (mapDependers.count(hash))
            {
                BOOST_FOREACH(COrphan* porphan, mapDependers[hash])
                {
                    if (!porphan->setDependsOn.empty())
                    {
                        porphan->setDependsOn.erase(hash);
                        if (porphan->setDependsOn.empty())
                            mapPriority.insert(make_pair(-porphan->dPriority, porphan->ptx));
                    }
                }
            }
        }

        nLastBlockTx = nBlockTx;
        nLastBlockSize = nBlockSize;
        if (fDebug && GetBoolArg("-printpriority"))
            printf("CreateNewBlock(): total size %lu\n", nBlockSize);

    }
    if (pblock->IsProofOfWork())
        pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits);

    // Fill in header
    pblock->hashPrevBlock  = pindexPrev->GetBlockHash();
    pblock->hashMerkleRoot = pblock->BuildMerkleTree();
    pblock->nTime          = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
    pblock->nTime          = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
    pblock->UpdateTime(pindexPrev);
    pblock->nNonce         = 0;

    return pblock.release();
}


void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
    // Update nExtraNonce
    static uint256 hashPrevBlock;
    if (hashPrevBlock != pblock->hashPrevBlock)
    {
        nExtraNonce = 0;
        hashPrevBlock = pblock->hashPrevBlock;
    }
    ++nExtraNonce;
    pblock->vtx[0].vin[0].scriptSig = (CScript() << pblock->nTime << CBigNum(nExtraNonce)) + COINBASE_FLAGS;
    assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100);

    pblock->hashMerkleRoot = pblock->BuildMerkleTree();
}
Code:
void BitcoinMiner(CWallet *pwallet, bool fProofOfStake)
{
    printf("CPUMiner started for proof-of-%s\n", fProofOfStake? "stake" : "work");
    SetThreadPriority(THREAD_PRIORITY_LOWEST);

    // Each thread has its own key and counter
    CReserveKey reservekey(pwallet);
    unsigned int nExtraNonce = 0;

    while (fGenerateBitcoins || fProofOfStake)
    {
        if (fShutdown)
            return;
        while (vNodes.empty() || IsInitialBlockDownload())
        {
            Sleep(1000);
            if (fShutdown)
                return;
            if ((!fGenerateBitcoins) && !fProofOfStake)
                return;
        }

        while (pwallet->IsLocked())
        {
            strMintWarning = strMintMessage;
            Sleep(1000);
        }
        strMintWarning = "";

        //
        // Create new block
        //
        unsigned int nTransactionsUpdatedLast = nTransactionsUpdated;
        CBlockIndex* pindexPrev = pindexBest;

        auto_ptr pblock(CreateNewBlock(pwallet, fProofOfStake));
        if (!pblock.get())
            return;

        IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce);

        if (fProofOfStake)
        {
            // ppcoin: if proof-of-stake block found then process block
            if (pblock->IsProofOfStake())
            {
                if (!pblock->SignBlock(*pwalletMain))
                {
                    strMintWarning = strMintMessage;
                    continue;
                }
                strMintWarning = "";
                printf("CPUMiner : proof-of-stake block found %s\n", pblock->GetHash().ToString().c_str());
                SetThreadPriority(THREAD_PRIORITY_NORMAL);
                CheckWork(pblock.get(), *pwalletMain, reservekey);
                SetThreadPriority(THREAD_PRIORITY_LOWEST);
            }
            Sleep(500);
            continue;
        }

        printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size());


        //
        // Prebuild hash buffers
        //
        char pmidstatebuf[32+16]; char* pmidstate = alignup<16>(pmidstatebuf);
        char pdatabuf[128+16];    char* pdata     = alignup<16>(pdatabuf);
        char phash1buf[64+16];    char* phash1    = alignup<16>(phash1buf);

        FormatHashBuffers(pblock.get(), pmidstate, pdata, phash1);

        unsigned int& nBlockTime = *(unsigned int*)(pdata + 64 + 4);
        unsigned int& nBlockNonce = *(unsigned int*)(pdata + 64 + 12);


        //
        // Search
        //
        int64 nStart = GetTime();
        uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
        uint256 hashbuf[2];
        uint256& hash = *alignup<16>(hashbuf);
        loop
        {
            unsigned int nHashesDone = 0;
            unsigned int nNonceFound;

            // Crypto++ SHA-256
            nNonceFound = ScanHash_CryptoPP(pmidstate, pdata + 64, phash1,
                                            (char*)&hash, nHashesDone);

            // Check if something found
            if (nNonceFound != (unsigned int) -1)
            {
                for (unsigned int i = 0; i < sizeof(hash)/4; i++)
                    ((unsigned int*)&hash)[i] = ByteReverse(((unsigned int*)&hash)[i]);

                if (hash <= hashTarget)
                {
                    // Found a solution
                    pblock->nNonce = ByteReverse(nNonceFound);
                    assert(hash == pblock->GetHash());
                    if (!pblock->SignBlock(*pwalletMain))
                    {
                        strMintWarning = strMintMessage;
                        break;
                    }
                    strMintWarning = "";
                    SetThreadPriority(THREAD_PRIORITY_NORMAL);
                    CheckWork(pblock.get(), *pwalletMain, reservekey);
                    SetThreadPriority(THREAD_PRIORITY_LOWEST);
                    break;
                }
            }

            // Meter hashes/sec
            static int64 nHashCounter;
            if (nHPSTimerStart == 0)
            {
                nHPSTimerStart = GetTimeMillis();
                nHashCounter = 0;
            }
            else
                nHashCounter += nHashesDone;
            if (GetTimeMillis() - nHPSTimerStart > 4000)
            {
                static CCriticalSection cs;
                {
                    LOCK(cs);
                    if (GetTimeMillis() - nHPSTimerStart > 4000)
                    {
                        dHashesPerSec = 1000.0 * nHashCounter / (GetTimeMillis() - nHPSTimerStart);
                        nHPSTimerStart = GetTimeMillis();
                        nHashCounter = 0;
                        static int64 nLogTime;
                        if (GetTime() - nLogTime > 30 * 60)
                        {
                            nLogTime = GetTime();
                            printf("%s ", DateTimeStrFormat(GetTime()).c_str());
                            printf("hashmeter %3d CPUs %6.0f khash/s\n", vnThreadsRunning[THREAD_MINER], dHashesPerSec/1000.0);
                        }
                    }
                }
            }

            // Check for stop or if block needs to be rebuilt
            if (fShutdown)
                return;
            if (!fGenerateBitcoins)
                return;
            if (fLimitProcessors && vnThreadsRunning[THREAD_MINER] > nLimitProcessors)
                return;
            if (vNodes.empty())
                break;
            if (nBlockNonce >= 0xffff0000)
                break;
            if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)
                break;
            if (pindexPrev != pindexBest)
                break;

            // Update nTime every few seconds
            pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
            pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
            pblock->UpdateTime(pindexPrev);
            nBlockTime = ByteReverse(pblock->nTime);
            if (pblock->GetBlockTime() >= (int64)pblock->vtx[0].nTime + nMaxClockDrift)
                break;  // need to update coinbase timestamp
        }
    }
}


There are also lots of curious "stake connection tests," which may or may not indicate the aforementioned centralized nature of this PoS method.

Stake blocks are always difficulty 1 and nonce zero.  Apparently the blockchain is programmed to always accept them as valid as long as a certain quantity of network time has passed and the quantity for which they are valid is predestined by the code included.

For whatever reason Sunny King decided not to bother publishing the pseudocode implementation of his algorithms in favour of the epic LaTeX narrative of his implementation, but if there are vulnerabilities you should be able to see them in the above code.
legendary
Activity: 2940
Merit: 1090
November 27, 2012, 11:33:21 AM
#10
If it will work it might be useful to that person who apparently has oodles of folk wanting to each start a separate chain, since basically if they sell more than half the company it makes sense the buyer should have control of it. They could spawn oodles of them so that there would be many many many small targets each available in effect for purchase by buying half their coins. Might work okay as a corporate shares system.

Maybe in fact they would be better than coloured coins because the majority owner's control would be kind of built in, instead of the sellers being able to wait for someone to buy most of their offering then go like ha ha so what you still have no power over the part we didn't yet sell to you, no control of the whole set so to speak.

-MarkM-
legendary
Activity: 1050
Merit: 1003
November 27, 2012, 11:21:19 AM
#9
Looking at the code, it seems to basically be solidcoin without maybe only one master-node instead of several master-nodes.
The PoW does nothing but generate currency. It doesn't try to secure anything. I have no idea how the master node works.

I tried to hack out all proof of work nd all checking of checkpoints but then both sides seem to wait for the other to start saying something.
[/quote]
As I understand it, the proof of stake relies on the proof of work to generate time stamps. The timestamps are then used to iterate a random process which allows stake generation. Unless proof-of-stake can also generate the timestamps, it might be necessary to leave proof-of-work in there to get it to run. If so, perhaps reduce the block reward to some trivial amount. There also is some management of the proportions of both types of blocks that would need to be adjusted to pure proof-of-stake.
 
So possibly it won't even do anything until it gets a go-ahead from the solid node or something.
Hmmm. remove that part?

Since without proof of work blocks coins have to come from somewhere, I set the minimum reward for a proof of stake block to one coin.
Fine by me. One coin is pretty trivial.

The fact they won't even talk to each other is nasty though, it seems to imply that there is expected to alredy be something out there to get and that someone you connect to will push it at you.
Can you reinsert bitcoin code that facilitates communication?

Cunicula it does seem likely you are correct that the whole proof of work thing is only there to provide an excuse for being controlled by the solid node(s) in the usual solid central control style system we have seen so many times before. I did try to put into the counting up of coin ages  max of 90 days per each coin it found.

Maybe my problem is there is no coin-age initilly so maybe it does not try to make a stake block thus does not get the free minimum one coin reward for making a stake block. i might have to make it always have one coin's worth of chance to try, or something, in the case where it has no coins initially.

An incentive to be online is for transactions to get processed, since with stake blocks being the only blocks no transactions will go through unless at least one node stays online long enough for a block to get created; and maybe also the less people who are online trying to make a stake block the longer it maybe might take for one to be made. 9that one lone person who made one maybe used all his coin-age so needs 90 days to recover... so initially it might need at least 90 wallets to have been created in order for one block a day to be able to be made...)

-MarkM-
My only comment on the rest of this is that I do not believe that it is completely a SolidCoin master node style system. There is underlying code underneath which may work.
Pages:
Jump to: