]> Git Repo - VerusCoin.git/blame - src/cc/lotto.cpp
Merge remote-tracking branch 'verus/dev'
[VerusCoin.git] / src / cc / lotto.cpp
CommitLineData
69829385 1/******************************************************************************
2 * Copyright © 2014-2018 The SuperNET Developers. *
3 * *
4 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at *
5 * the top-level directory of this distribution for the individual copyright *
6 * holder information and the developer policies on copyright and licensing. *
7 * *
8 * Unless otherwise agreed in a custom licensing agreement, no part of the *
9 * SuperNET software, including this file may be copied, modified, propagated *
10 * or distributed except according to the terms contained in the LICENSE file *
11 * *
12 * Removal or modification of this copyright notice is prohibited. *
13 * *
14 ******************************************************************************/
15
16#include "CClotto.h"
17#include "../txmempool.h"
18
19/*
e0f78699 20 A blockchain lotto has the problem of generating the deterministic random numbers needed to get a winner in a way that doesnt allow cheating. If we save the entropy for later publishing and display the hash of the entropy, it is true that the players wont know what the entropy value is, however the creator of the lotto funds will be able to know and simply create a winning ticket when the jackpot is large enough.
21
22 We also need to avoid chain reorgs from disclosing the entropy and then allowing people to submit a winning ticket calculated based on the disclosed entropy (see attack vector in dice.cpp)
23
24 As usual it needs to be provably fair and random
25
26 The solution is for everybody to post the hash of their entropy when purchasing tickets. Then at the time of the drawing, nodes would post their entropy over an N block period to avoid censorship attack. After the N block period, then we have valid entropy that we know was locked in prior to the start of the N blocks and that nobody would have been able to know ahead of time the final entropy value.
27
28 As long as one node submits a high entropy value, then just by combining all the submissions together, we get the drawing's entropy value. Given that, the usual process can determine if there was a winner at the specified odds. In fact, all the nodes are able to determine exactly how many winners there were and whether to validate 1/w payouts to the w winners or rollover the jackpot to the next drawing.
29
30 To remove the need for an autopayout, the winning node(s) would need to submit a 1/w payout tx, this would be able to be done at any time and the winner does not have to have submitted proof of hentropy. In order to prevent a player from opportunistically withholding their entropy, the lotto creator will post the original proof of hentropy after the N block player submission period. This masks to all the players the final value of entropy.
31
32 Attack vector: the lotto creator can have many player tickets in reserve all with their entropy ready to submit, but based on the actual submissions, find the one which gives him the best outcome. since all the player submissions will be known via mempool, along with the original hentropy. However the lotto creator would have to mine the final block in order to know the order of the player tickets.
33
34 Thinking about this evil miner attack, it seems pretty bad, so a totally new approach is needed. Preferably with a simple enough protocol. Let us remove any special knowledge by the lotto creator, so like the faucet, it seems just that there is a single lotto for a chain.
35
36 >>>>>>>>>>>> second iteration
37
38 What we need is something that gives each ticket an equal chance at the jackpot, without allowing miner or relayer to gain an advantage. ultimately the jackpot payout tx needs to be confirmed, so there needs to be some number of blocks to make a claim to avoid censorship attack. If onchain entropy is needed, then it should be reduced to 1 bit per block to reduce the grinding that is possible. This does mean a block miner for the last bit of entropy can double their chances at winning, but the alternative is to have an external source of entropy, which creates its own set of issues like what prevents the nodes getting the external entropy from cheating?
39
40 Conveniently the lotto mechanics are similar to a PoS staking, so it can be based on everybody trying to stake a single lotto jackpot.
41
42 The calculation would need to be based on the payout address and utxosize, so relayers cant intercept it to steal the jackpot.
43
44 each jackpot would effectively restart the lotto
45
46 the funds from new lotto tickets can be spent by the jackpot, but those tickets can still win the new jackpot
47
48 each set of tickets (utxo) would become eligible to claim the jackpot after some time is elapsed so the entropy for that utxo can be obtained. [6 bits * 32 + 1 bit * 16] 48 blocks
49
50It is possible to have a jackpot but miss out on it due to not claiming it. To minimize the effect from this, each ticket would have one chance to win, which can be calculated and a jackpot claim submitted just once.
51
52 in order to randomize the timing of claim, a txid PoW similar to faucetget will maximize the chance of only a single jackpot txid that can propagate throughout the mempools, which will prevent the second one broadcast. Granted the mining node can override this if they also have a winning ticket, but assuming the PoS lottery makes it unlikely for two winners in a single block, this is not a big issue.
53
54 In order to adapt the difficulty of winning the lotto, but not requiring recalculating all past tickets, as new lotto tickets are sold without a jackpot, it needs to become easier to win. Basically as the lotto jackpot gets bigger and bigger, it keeps getting easier to win! This convergence will avoid having unwinnable jackpots.
55
56 rpc calls
57 lottoinfo
58 lottotickets <numtickets>
59 lottostatus
60 lottowinner tickethash ticketid
61
69829385 62*/
63
64// start of consensus code
65
59da6d30 66int64_t IsLottovout(struct CCcontract_info *cp,const CTransaction& tx,int32_t v)
69829385 67{
68 char destaddr[64];
69 if ( tx.vout[v].scriptPubKey.IsPayToCryptoCondition() != 0 )
70 {
71 if ( Getscriptaddress(destaddr,tx.vout[v].scriptPubKey) > 0 && strcmp(destaddr,cp->unspendableCCaddr) == 0 )
72 return(tx.vout[v].nValue);
73 }
74 return(0);
75}
76
77bool LottoExactAmounts(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx,int32_t minage,uint64_t txfee)
78{
79 static uint256 zerohash;
59da6d30 80 CTransaction vinTx; uint256 hashBlock,activehash; int32_t i,numvins,numvouts; int64_t inputs=0,outputs=0,assetoshis;
69829385 81 numvins = tx.vin.size();
82 numvouts = tx.vout.size();
83 for (i=0; i<numvins; i++)
84 {
85 //fprintf(stderr,"vini.%d\n",i);
86 if ( (*cp->ismyvin)(tx.vin[i].scriptSig) != 0 )
87 {
88 //fprintf(stderr,"vini.%d check mempool\n",i);
89 if ( eval->GetTxUnconfirmed(tx.vin[i].prevout.hash,vinTx,hashBlock) == 0 )
90 return eval->Invalid("cant find vinTx");
91 else
92 {
93 //fprintf(stderr,"vini.%d check hash and vout\n",i);
94 if ( hashBlock == zerohash )
95 return eval->Invalid("cant Lotto from mempool");
96 if ( (assetoshis= IsLottovout(cp,vinTx,tx.vin[i].prevout.n)) != 0 )
97 inputs += assetoshis;
98 }
99 }
100 }
101 for (i=0; i<numvouts; i++)
102 {
103 //fprintf(stderr,"i.%d of numvouts.%d\n",i,numvouts);
104 if ( (assetoshis= IsLottovout(cp,tx,i)) != 0 )
105 outputs += assetoshis;
106 }
e0f78699 107 if ( inputs != outputs+txfee )
69829385 108 {
109 fprintf(stderr,"inputs %llu vs outputs %llu\n",(long long)inputs,(long long)outputs);
e0f78699 110 return eval->Invalid("mismatched inputs != outputs + txfee");
69829385 111 }
112 else return(true);
113}
114
8a727a26 115bool LottoValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx, uint32_t nIn)
69829385 116{
117 int32_t numvins,numvouts,preventCCvins,preventCCvouts,i; bool retval;
6deb8c09 118 return(false); // reject any lotto CC for now
69829385 119 numvins = tx.vin.size();
120 numvouts = tx.vout.size();
121 preventCCvins = preventCCvouts = -1;
122 if ( numvouts < 1 )
123 return eval->Invalid("no vouts");
124 else
125 {
126 //fprintf(stderr,"check vins\n");
127 for (i=0; i<numvins; i++)
128 {
129 if ( IsCCInput(tx.vin[0].scriptSig) == 0 )
130 {
131 fprintf(stderr,"Lottoget invalid vini\n");
132 return eval->Invalid("illegal normal vini");
133 }
134 }
135 //fprintf(stderr,"check amounts\n");
136 if ( LottoExactAmounts(cp,eval,tx,1,10000) == false )
137 {
138 fprintf(stderr,"Lottoget invalid amount\n");
139 return false;
140 }
141 else
142 {
143 preventCCvouts = 1;
144 if ( IsLottovout(cp,tx,0) != 0 )
145 {
146 preventCCvouts++;
147 i = 1;
148 } else i = 0;
149 if ( tx.vout[i].nValue != COIN )
150 return eval->Invalid("invalid Lotto output");
151 retval = PreventCC(eval,tx,preventCCvins,numvins,preventCCvouts,numvouts);
152 if ( retval != 0 )
153 fprintf(stderr,"Lottoget validated\n");
154 else fprintf(stderr,"Lottoget invalid\n");
155 return(retval);
156 }
157 }
158}
159// end of consensus code
160
161// helper functions for rpc calls in rpcwallet.cpp
162
59da6d30 163int64_t AddLottoInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,int64_t total,int32_t maxinputs)
69829385 164{
59da6d30 165 char coinaddr[64]; int64_t nValue,price,totalinputs = 0; uint256 txid,hashBlock; std::vector<uint8_t> origpubkey; CTransaction vintx; int32_t n = 0;
69829385 166 std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
167 GetCCaddress(cp,coinaddr,pk);
168 SetCCunspents(unspentOutputs,coinaddr);
169 for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++)
170 {
171 txid = it->first.txhash;
c6c490af 172 // prevent dup
e0f78699 173 if ( it->second.satoshis < COIN )
93ac96fa 174 continue;
69829385 175 if ( GetTransaction(txid,vintx,hashBlock,false) != 0 )
176 {
177 if ( (nValue= IsLottovout(cp,vintx,(int32_t)it->first.index)) > 0 )
178 {
179 if ( total != 0 && maxinputs != 0 )
180 mtx.vin.push_back(CTxIn(txid,(int32_t)it->first.index,CScript()));
181 nValue = it->second.satoshis;
182 totalinputs += nValue;
183 n++;
184 if ( (total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs) )
185 break;
186 }
187 }
188 }
189 return(totalinputs);
190}
191
e0f78699 192uint8_t DecodeLottoFundingOpRet(const CScript &scriptPubKey,uint64_t &sbits,int32_t ticketsize,int32_t odds,int32_t firstheight,int32_t period,uint256 hentropy)
193{
194 std::vector<uint8_t> vopret; uint8_t *script,e,f;
195 GetOpReturnData(scriptPubKey, vopret);
196 script = (uint8_t *)vopret.data();
197 if ( vopret.size() > 2 && E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> sbits; ss >> ticketsize; ss >> odds; ss >> firstheight; ss >> period; ss >> hentropy) != 0 )
198 {
199 if ( e == EVAL_LOTTO && f == 'F' )
200 return(f);
201 }
202 return(0);
203}
204
205int64_t LottoPlanFunds(uint64_t refsbits,struct CCcontract_info *cp,CPubKey pk,uint256 reffundingtxid)
206{
8ab53441 207 char coinaddr[64]; uint64_t sbits; int64_t nValue,lockedfunds; uint256 txid,hashBlock,fundingtxid; CTransaction tx; int32_t vout; uint8_t funcid;
e0f78699 208 std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
209 lockedfunds = 0;
210 GetCCaddress(cp,coinaddr,pk);
211 SetCCunspents(unspentOutputs,coinaddr);
212 for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++)
213 {
214 txid = it->first.txhash;
215 vout = (int32_t)it->first.index;
216 if ( GetTransaction(txid,tx,hashBlock,false) != 0 && tx.vout[vout].scriptPubKey.IsPayToCryptoCondition() != 0 )
217 {
b377f869 218 // need to implement this! if ( (funcid= DecodeLottoOpRet(txid,tx.vout[tx.vout.size()-1].scriptPubKey,sbits,fundingtxid)) == 'F' || funcid == 'T' )
e0f78699 219 {
220 if ( refsbits == sbits && (funcid == 'F' && reffundingtxid == txid) || reffundingtxid == fundingtxid )
221 {
222 if ( (nValue= IsLottovout(cp,tx,vout)) > 0 )
223 lockedfunds += nValue;
224 else fprintf(stderr,"refsbits.%llx sbits.%llx nValue %.8f\n",(long long)refsbits,(long long)sbits,(double)nValue/COIN);
225 } //else fprintf(stderr,"else case\n");
8ab53441 226 } //else fprintf(stderr,"funcid.%d %c skipped %.8f\n",funcid,funcid,(double)tx.vout[vout].nValue/COIN);
e0f78699 227 }
228 }
229 return(lockedfunds);
230}
231
232UniValue LottoInfo(uint256 lottoid)
233{
234 UniValue result(UniValue::VOBJ); uint256 hashBlock,hentropy; CTransaction vintx; uint64_t lockedfunds,sbits; int32_t ticketsize,odds,firstheight,period; CPubKey lottopk; struct CCcontract_info *cp,C; char str[67],numstr[65];
235 if ( GetTransaction(lottoid,vintx,hashBlock,false) == 0 )
236 {
237 fprintf(stderr,"cant find lottoid\n");
238 result.push_back(Pair("result","error"));
239 result.push_back(Pair("error","cant find lottoid"));
240 return(result);
241 }
242 if ( vintx.vout.size() > 0 && DecodeLottoFundingOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,sbits,ticketsize,odds,firstheight,period,hentropy) == 0 )
243 {
244 fprintf(stderr,"lottoid isnt lotto creation txid\n");
245 result.push_back(Pair("result","error"));
246 result.push_back(Pair("error","lottoid isnt lotto creation txid"));
247 return(result);
248 }
249 result.push_back(Pair("result","success"));
250 result.push_back(Pair("lottoid",uint256_str(str,lottoid)));
251 unstringbits(str,sbits);
252 result.push_back(Pair("name",str));
253 result.push_back(Pair("sbits",sbits));
254 result.push_back(Pair("ticketsize",ticketsize));
255 result.push_back(Pair("odds",odds));
256 cp = CCinit(&C,EVAL_LOTTO);
257 lottopk = GetUnspendable(cp,0);
258 lockedfunds = LottoPlanFunds(sbits,cp,lottopk,lottoid);
259 sprintf(numstr,"%.8f",(double)lockedfunds/COIN);
260 result.push_back(Pair("jackpot",numstr));
261 return(result);
262}
263
264UniValue LottoList()
265{
266 UniValue result(UniValue::VARR); std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex; struct CCcontract_info *cp,C; uint256 txid,hashBlock,hentropy; CTransaction vintx; uint64_t sbits; int32_t ticketsize,odds,firstheight,period; char str[65];
267 cp = CCinit(&C,EVAL_LOTTO);
268 SetCCtxids(addressIndex,cp->normaladdr);
269 for (std::vector<std::pair<CAddressIndexKey, CAmount> >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++)
270 {
271 txid = it->first.txhash;
272 if ( GetTransaction(txid,vintx,hashBlock,false) != 0 )
273 {
274 if ( vintx.vout.size() > 0 && DecodeLottoFundingOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,sbits,ticketsize,odds,firstheight,period,hentropy) == 'F' )
275 {
276 result.push_back(uint256_str(str,txid));
277 }
278 }
279 }
280 return(result);
281}
282
283std::string LottoCreate(uint64_t txfee,char *planstr,int64_t funding,int32_t ticketsize,int32_t odds,int32_t firstheight,int32_t period)
284{
55d4ab05 285 CMutableTransaction mtx; uint256 entropy,hentropy; CPubKey mypk,lottopk; uint64_t sbits; int64_t inputs,CCchange=0,nValue=COIN; struct CCcontract_info *cp,C;
e0f78699 286 cp = CCinit(&C,EVAL_LOTTO);
287 if ( txfee == 0 )
288 txfee = 10000;
289 lottopk = GetUnspendable(cp,0);
290 mypk = pubkey2pk(Mypubkey());
291 sbits = stringbits(planstr);
292 if ( AddNormalinputs(mtx,mypk,funding+txfee,60) > 0 )
293 {
294 hentropy = DiceHashEntropy(entropy,mtx.vin[0].prevout.hash);
295 mtx.vout.push_back(MakeCC1vout(EVAL_LOTTO,funding,lottopk));
296 return(FinalizeCCTx(0,cp,mtx,mypk,txfee,CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)EVAL_LOTTO << (uint8_t)'F' << sbits << ticketsize << odds << firstheight << period << hentropy)));
297 }
298}
299
300std::string LottoTicket(uint64_t txfee,uint256 lottoid,int64_t numtickets)
69829385 301{
e0f78699 302 CMutableTransaction mtx; CPubKey mypk,lottopk; CScript opret; int64_t inputs,CCchange=0,nValue=COIN; struct CCcontract_info *cp,C;
69829385 303 cp = CCinit(&C,EVAL_LOTTO);
304 if ( txfee == 0 )
305 txfee = 10000;
e0f78699 306 lottopk = GetUnspendable(cp,0);
69829385 307 mypk = pubkey2pk(Mypubkey());
e0f78699 308 if ( (inputs= AddLottoInputs(cp,mtx,lottopk,nValue+txfee,60)) > 0 )
69829385 309 {
310 if ( inputs > nValue )
311 CCchange = (inputs - nValue - txfee);
312 if ( CCchange != 0 )
e0f78699 313 mtx.vout.push_back(MakeCC1vout(EVAL_LOTTO,CCchange,lottopk));
69829385 314 mtx.vout.push_back(CTxOut(nValue,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG));
d2aba1d6 315 return(FinalizeCCTx(-1LL,cp,mtx,mypk,txfee,opret));
69829385 316 } else fprintf(stderr,"cant find Lotto inputs\n");
1ed46fb8 317 return("");
69829385 318}
319
320std::string LottoWinner(uint64_t txfee)
321{
e0f78699 322 CMutableTransaction mtx; CPubKey mypk,lottopk; int64_t winnings = 0; CScript opret; struct CCcontract_info *cp,C;
69829385 323 cp = CCinit(&C,EVAL_LOTTO);
324 if ( txfee == 0 )
325 txfee = 10000;
326 mypk = pubkey2pk(Mypubkey());
e0f78699 327 lottopk = GetUnspendable(cp,0);
90a233e9 328 if ( AddNormalinputs(mtx,mypk,txfee,64) > 0 )
69829385 329 {
e0f78699 330 mtx.vout.push_back(MakeCC1vout(EVAL_LOTTO,winnings,lottopk));
6e281d1c 331 return(FinalizeCCTx(0,cp,mtx,mypk,txfee,opret));
69829385 332 }
1ed46fb8 333 return("");
69829385 334}
335
336
This page took 0.147822 seconds and 4 git commands to generate.