Initial dice

This commit is contained in:
jl777
2018-07-30 07:56:27 -11:00
parent e0e985ee17
commit 7eb604110e

View File

@@ -32,6 +32,59 @@
The house node monitors the 'B' transactions and if it sees one of its own, it creates either a winner 'W' or loser 'L' transaction, with proof of hash of entropy. The house node monitors the 'B' transactions and if it sees one of its own, it creates either a winner 'W' or loser 'L' transaction, with proof of hash of entropy.
In the even the house node doesnt respond before timeoutblocks, then anybody (including bettor) can undo the bet with funds going back to the house and bettor In the even the house node doesnt respond before timeoutblocks, then anybody (including bettor) can undo the bet with funds going back to the house and bettor
In order for people to play dice, someone (anyone) needs to create a funded dice plan and addfunding with enough utxo to allow players to find one.
createfunding:
vins.*: normal inputs
vout.0: CC vout for funding
vout.1: normal marker vout for easy searching
vout.2: normal change
vout.n-1: opreturn 'F' sbits minbet maxbet maxodds timeoutblocks
addfunding (entropy):
vins.*: normal inputs
vout.0: CC vout for locked entropy funds
vout.1: tag to owner address for entropy funds
vout.2: normal change
vout.n-1: opreturn 'E' sbits fundingtxid hentropy
bet:
vin.0: entropy txid from house
vins.1+: normal inputs
vout.0: CC vout for locked entropy
vout.1: CC vout for locked bet
vout.2: tag for bettor's address (txfee + odds)
vout.3: change
vout.n-1: opreturn 'B' sbits fundingtxid entropy
winner:
vin.0: betTx CC vout.0 entropy from bet
vin.1: betTx CC vout.1 bet amount from bet
vin.2+: funding CC vout.0 from 'F', 'E', 'W', 'L' or 'R'
vout.0: funding CC change to entropy owner
vout.1: tag to owner address for entropy funds
vout.2: normal output to bettor's address
vout.n-1: opreturn 'W' sbits fundingtxid hentropy
loser:
vin.0: betTx CC vout.0 entropy from bet
vin.1: betTx CC vout.1 bet amount from bet
vin.2+: funding CC vout.0 from 'F', 'E', 'W', 'L' or 'R'
vout.0: funding CC to entropy owner
vout.1: tag to owner address for entropy funds
vout.n-1: opreturn 'L' sbits fundingtxid hentropy
refund:
vin.0: betTx CC vout.0 entropy from bet
vin.1: betTx CC vout.1 bet amount from bet
vin.2+: funding CC vout.0 from 'F', 'E', 'W', 'L' or 'R'
vout.0: funding CC vin.0 to entropy owner
vout.1: tag to owner address for entropy funds
vout.2: normal vin.1 to bettor's address
vout.n-1: opreturn 'R' sbits fundingtxid hentropy
*/ */
#include "../endian.h" #include "../endian.h"
@@ -157,7 +210,7 @@ uint8_t DecodeDiceOpRet(uint256 txid,const CScript &scriptPubKey,uint64_t &sbits
} }
else if ( E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> sbits; ss >> fundingtxid; ss >> hash) != 0 ) else if ( E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> sbits; ss >> fundingtxid; ss >> hash) != 0 )
{ {
if ( e == EVAL_DICE && (f == 'B' || f == 'U' || f == 'E') ) if ( e == EVAL_DICE && (f == 'B' || f == 'W' || f == 'L' || f == 'R' || f == 'E') )
return(f); return(f);
//else fprintf(stderr,"mismatched e.%02x f.(%c)\n",e,f); //else fprintf(stderr,"mismatched e.%02x f.(%c)\n",e,f);
} }
@@ -228,13 +281,13 @@ bool DiceIsmine(const CScript scriptPubKey)
return(strcmp(destaddr,myaddr) == 0); return(strcmp(destaddr,myaddr) == 0);
} }
int32_t DiceIsWinner(uint256 txid,CTransaction tx,CTransaction vinTx,uint256 bettorentropy,uint64_t sbits,int64_t minbet,int64_t maxbet,int64_t maxodds,int64_t timeoutblocks,uint256 fundingtxid) int32_t DiceIsWinner(int32_t mustbeme,uint256 txid,CTransaction tx,CTransaction vinTx,uint256 bettorentropy,uint64_t sbits,int64_t minbet,int64_t maxbet,int64_t maxodds,int64_t timeoutblocks,uint256 fundingtxid)
{ {
uint64_t vinsbits,winnings; uint256 vinfundingtxid,hentropy,hentropy2,entropy; uint64_t vinsbits,winnings; uint256 vinfundingtxid,hentropy,hentropy2,entropy; uint8_t funcid;
//char str[65],str2[65]; //char str[65],str2[65];
if ( DiceIsmine(vinTx.vout[1].scriptPubKey) != 0 && vinTx.vout.size() > 0 ) if ( (mustbeme == 0 || DiceIsmine(vinTx.vout[1].scriptPubKey) != 0) && vinTx.vout.size() > 0 )
{ {
if ( DecodeDiceOpRet(txid,vinTx.vout[vinTx.vout.size()-1].scriptPubKey,vinsbits,vinfundingtxid,hentropy) == 'E' && sbits == vinsbits && fundingtxid == vinfundingtxid ) if ( ((funcid= DecodeDiceOpRet(txid,vinTx.vout[vinTx.vout.size()-1].scriptPubKey,vinsbits,vinfundingtxid,hentropy)) == 'E' || funcid == 'W' || funcid == 'L' || funcid == 'R') && sbits == vinsbits && fundingtxid == vinfundingtxid )
{ {
hentropy2 = DiceHashEntropy(entropy,vinTx.vin[0].prevout.hash); hentropy2 = DiceHashEntropy(entropy,vinTx.vin[0].prevout.hash);
if ( hentropy == hentropy2 ) if ( hentropy == hentropy2 )
@@ -281,7 +334,7 @@ bool DiceValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx)
//vout.0: CC vout for funding //vout.0: CC vout for funding
//vout.1: normal marker vout for easy searching //vout.1: normal marker vout for easy searching
//vout.2: normal change //vout.2: normal change
//vout.n-1: opreturn 'F' sbits APR minseconds maxseconds mindeposit //vout.n-1: opreturn 'F' sbits minbet maxbet maxodds timeoutblocks
return eval->Invalid("unexpected DiceValidate for createfunding"); return eval->Invalid("unexpected DiceValidate for createfunding");
break; break;
case 'E': case 'E':
@@ -300,27 +353,40 @@ bool DiceValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx)
//vout.2: tag for bettor's address (txfee + odds) //vout.2: tag for bettor's address (txfee + odds)
//vout.3: change //vout.3: change
//vout.n-1: opreturn 'B' sbits fundingtxid entropy //vout.n-1: opreturn 'B' sbits fundingtxid entropy
// get house hentropy and its vin0.prevtxid, cmp vout1 to owner address
// if owneraddress is me, then validate hentropy and submit outcome tx
if ( eval->GetTxUnconfirmed(tx.vin[0].prevout.hash,vinTx,hashBlock) == 0 ) if ( eval->GetTxUnconfirmed(tx.vin[0].prevout.hash,vinTx,hashBlock) == 0 )
return eval->Invalid("always should find vin.0, but didnt"); return eval->Invalid("always should find vin.0, but didnt");
if ( (iswin= DiceIsWinner(txid,tx,vinTx,hash,sbits,minbet,maxbet,maxodds,timeoutblocks,fundingtxid)) != 0 ) if ( (iswin= DiceIsWinner(1,txid,tx,vinTx,hash,sbits,minbet,maxbet,maxodds,timeoutblocks,fundingtxid)) != 0 )
{ {
fprintf(stderr,"DiceIsWinner.%d\n",iswin); fprintf(stderr,"DiceIsWinner.%d\n",iswin);
} }
//return eval->Invalid("dont confirm bet during debug"); //return eval->Invalid("dont confirm bet during debug");
break; break;
case 'L': case 'L':
//vin.0: betTx CC vout.0 entropy from bet
//vin.1: betTx CC vout.1 bet amount from bet
//vin.2+: funding CC vout.0 from 'F', 'E', 'W', 'L' or 'R'
//vout.0: funding CC to entropy owner
//vout.1: tag to owner address for entropy funds
//vout.n-1: opreturn 'L' sbits fundingtxid hentropy
break; break;
case 'W': case 'W':
//vin.0: betTx CC vout.0 entropy from bet
//vin.1: betTx CC vout.1 bet amount from bet
//vin.2+: funding CC vout.0 from 'F', 'E', 'W', 'L' or 'R'
//vout.0: funding CC change to entropy owner
//vout.1: tag to owner address for entropy funds
//vout.2: normal output to bettor's address
//vout.n-1: opreturn 'W' sbits fundingtxid hentropy
break; break;
case 'U': case 'R':
//vin.0: locked funds CC vout.0 from lock //vin.0: betTx CC vout.0 entropy from bet
//vin.1+: funding CC vout.0 from 'F' and 'E' and 'U' //vin.1: betTx CC vout.1 bet amount from bet
//vout.0: funding CC change //vin.2+: funding CC vout.0 from 'F', 'E', 'W', 'L' or 'R'
//vout.1: normal output to unlock address //vout.0: funding CC vin.0 to entropy owner
//vout.n-1: opreturn 'U' sbits fundingtxid //vout.1: tag to owner address for entropy funds
for (i=0; i<numvins; i++) //vout.2: normal vin.1 to bettor's address
//vout.n-1: opreturn 'R' sbits fundingtxid hentropy
/*for (i=0; i<numvins; i++)
{ {
if ( (*cp->ismyvin)(tx.vin[i].scriptSig) == 0 ) if ( (*cp->ismyvin)(tx.vin[i].scriptSig) == 0 )
return eval->Invalid("unexpected normal vin for unlock"); return eval->Invalid("unexpected normal vin for unlock");
@@ -344,7 +410,7 @@ bool DiceValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx)
//reward = DiceCalc(amount,tx.vin[0].prevout.hash,minbet,maxbet,maxodds,timeoutblocks); //reward = DiceCalc(amount,tx.vin[0].prevout.hash,minbet,maxbet,maxodds,timeoutblocks);
if ( tx.vout[1].nValue > amount+reward ) if ( tx.vout[1].nValue > amount+reward )
return eval->Invalid("unlock tx vout.1 isnt amount+reward"); return eval->Invalid("unlock tx vout.1 isnt amount+reward");
preventCCvouts = 1; preventCCvouts = 1;*/
break; break;
} }
} }
@@ -353,7 +419,6 @@ bool DiceValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx)
return(true); return(true);
} }
// 'B' vs 'F' and 'E'
uint64_t AddDiceInputs(CScript &scriptPubKey,int32_t fundsflag,struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,uint64_t total,int32_t maxinputs) uint64_t AddDiceInputs(CScript &scriptPubKey,int32_t fundsflag,struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,uint64_t total,int32_t maxinputs)
{ {
char coinaddr[64],str[65]; uint64_t sbits,nValue,totalinputs = 0; uint256 txid,hash,hashBlock,fundingtxid; CTransaction tx; int32_t j,vout,n = 0; uint8_t funcid; char coinaddr[64],str[65]; uint64_t sbits,nValue,totalinputs = 0; uint256 txid,hash,hashBlock,fundingtxid; CTransaction tx; int32_t j,vout,n = 0; uint8_t funcid;
@@ -375,9 +440,9 @@ uint64_t AddDiceInputs(CScript &scriptPubKey,int32_t fundsflag,struct CCcontract
if ( (funcid= DecodeDiceOpRet(txid,tx.vout[tx.vout.size()-1].scriptPubKey,sbits,fundingtxid,hash)) != 0 ) if ( (funcid= DecodeDiceOpRet(txid,tx.vout[tx.vout.size()-1].scriptPubKey,sbits,fundingtxid,hash)) != 0 )
{ {
fprintf(stderr,"fundsflag.%d (%c) %.8f %.8f\n",fundsflag,funcid,(double)tx.vout[vout].nValue/COIN,(double)it->second.satoshis/COIN); fprintf(stderr,"fundsflag.%d (%c) %.8f %.8f\n",fundsflag,funcid,(double)tx.vout[vout].nValue/COIN,(double)it->second.satoshis/COIN);
if ( fundsflag != 0 && funcid != 'F' && funcid != 'E' && funcid != 'U' ) if ( fundsflag != 0 && funcid != 'F' && funcid != 'E' && funcid != 'W' && funcid != 'L' && funcid != 'R' )
continue; continue;
else if ( fundsflag == 0 && (funcid != 'B' || tx.vout.size() < 4) ) else if ( fundsflag == 0 && funcid != 'B' )
continue; continue;
if ( total != 0 && maxinputs != 0 ) if ( total != 0 && maxinputs != 0 )
{ {
@@ -412,11 +477,11 @@ uint64_t DicePlanFunds(uint64_t &entropyval,uint256 &entropytxid,uint64_t refsbi
{ {
if ( (funcid == 'F' && reffundingtxid == txid) || reffundingtxid == fundingtxid ) if ( (funcid == 'F' && reffundingtxid == txid) || reffundingtxid == fundingtxid )
{ {
if ( refsbits == sbits && (nValue= IsDicevout(cp,tx,vout)) > 0 ) if ( funcid != 'B' && refsbits == sbits && (nValue= IsDicevout(cp,tx,vout)) > 0 )
{ {
totalinputs += nValue; totalinputs += nValue;
fprintf(stderr,"add %.8f\n",(double)nValue/COIN); fprintf(stderr,"add %.8f\n",(double)nValue/COIN);
if ( first == 0 && funcid == 'E' ) if ( first == 0 && (funcid == 'E' || funcid == 'W' || funcid == 'L' || funcid == 'R') )
{ {
entropytxid = txid; entropytxid = txid;
entropyval = tx.vout[0].nValue; entropyval = tx.vout[0].nValue;
@@ -584,7 +649,7 @@ std::string DiceBet(uint64_t txfee,char *planstr,uint256 fundingtxid,int64_t bet
fprintf(stderr,"Dice plan %s illegal bet %.8f: minbet %.8f maxbet %.8f or odds %d vs max.%d\n",planstr,(double)bet/COIN,(double)minbet/COIN,(double)maxbet/COIN,(int32_t)odds,(int32_t)maxodds); fprintf(stderr,"Dice plan %s illegal bet %.8f: minbet %.8f maxbet %.8f or odds %d vs max.%d\n",planstr,(double)bet/COIN,(double)minbet/COIN,(double)maxbet/COIN,(int32_t)odds,(int32_t)maxodds);
return(0); return(0);
} }
if ( (funding= DicePlanFunds(entropyval,entropytxid,sbits,cp,dicepk,fundingtxid)) >= bet*odds+txfee && entropyval != 0 ) if ( (funding= DicePlanFunds(entropyval,entropytxid,sbits,cp,dicepk,fundingtxid)) >= 2*bet*odds+txfee && entropyval != 0 )
{ {
mtx.vin.push_back(CTxIn(entropytxid,0,CScript())); mtx.vin.push_back(CTxIn(entropytxid,0,CScript()));
if ( AddNormalinputs(mtx,mypk,bet+2*txfee+odds,60) > 0 ) if ( AddNormalinputs(mtx,mypk,bet+2*txfee+odds,60) > 0 )
@@ -604,73 +669,60 @@ std::string DiceBet(uint64_t txfee,char *planstr,uint256 fundingtxid,int64_t bet
std::string DiceWinLoseRefund(uint64_t txfee,char *planstr,uint256 fundingtxid,uint256 bettxid,int32_t winloserefund) std::string DiceWinLoseRefund(uint64_t txfee,char *planstr,uint256 fundingtxid,uint256 bettxid,int32_t winloserefund)
{ {
CMutableTransaction mtx; CTransaction betTx,entropyTx; uint256 hashBlock,bettorentropy,entropy,hentropy; CPubKey mypk,dicepk; struct CCcontract_info *cp,C; int64_t minbet,maxbet,maxodds,timeoutblocks; uint64_t sbits,amount = 0; uint8_t funcid; int32_t iswin; CMutableTransaction mtx; CTransaction betTx,entropyTx; uint256 entropytxid,hashBlock,bettorentropy,entropy,hentropy; CScript scriptPubKey; CPubKey mypk,dicepk; struct CCcontract_info *cp,C; int64_t inputs,CCchange=0,odds,fundsneeded; uint8_t funcid; int32_t iswin; uint64_t entropyval;
if ( (cp= Diceinit(fundingtxid,&C,planstr,txfee,mypk,dicepk,sbits,minbet,maxbet,maxodds,timeoutblocks)) == 0 ) if ( (cp= Diceinit(fundingtxid,&C,planstr,txfee,mypk,dicepk,sbits,minbet,maxbet,maxodds,timeoutblocks)) == 0 )
return(0); return(0);
if ( GetTransaction(bettxid,betTx,hashBlock,false) != 0 && GetTransaction(betTx.vin[0].prevout.hash,entropyTx,hashBlock,false) != 0 ) if ( GetTransaction(bettxid,betTx,hashBlock,false) != 0 && GetTransaction(betTx.vin[0].prevout.hash,entropyTx,hashBlock,false) != 0 )
{ {
bettorentropy = DiceGetEntropy(betTx,'B'); bettorentropy = DiceGetEntropy(betTx,'B');
if ( (iswin= DiceIsWinner(bettxid,betTx,entropyTx,bettorentropy,sbits,minbet,maxbet,maxodds,timeoutblocks,fundingtxid)) != 0 ) if ( (iswin= DiceIsWinner(0,bettxid,betTx,entropyTx,bettorentropy,sbits,minbet,maxbet,maxodds,timeoutblocks,fundingtxid)) != 0 )
{ {
if ( iswin == winloserefund ) if ( iswin == winloserefund )
{ {
if ( iswin == 0 )
funcid = 'R';
else if ( iswin > 0 )
funcid = 'W';
else funcid = 'L';
fprintf(stderr,"iswin.%d matches\n",iswin); fprintf(stderr,"iswin.%d matches\n",iswin);
if ( AddNormalinputs(mtx,mypk,amount+2*txfee,64) > 0 ) mtx.vin.push_back(CTxIn(bettxid,0,CScript()));
mtx.vin.push_back(CTxIn(bettxid,1,CScript()));
if ( iswin == 0 )
{ {
mtx.vout.push_back(MakeCC1vout(cp->evalcode,amount,dicepk)); funcid = 'R';
mtx.vout.push_back(CTxOut(txfee,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG)); fprintf(stderr,"refunds are not supported yet\n");
return(FinalizeCCTx(0,cp,mtx,mypk,txfee,EncodeDiceOpRet(funcid,sbits,fundingtxid,zeroid))); }
} else fprintf(stderr,"cant find enough inputs\n"); else if ( iswin > 0 )
{
funcid = 'W';
odds = (betTx.vout[2].nValue - txfee);
if ( odds < 1 || odds > maxodds )
{
fprintf(stderr,"illegal odds.%d vs maxodds.%d\n",(int32_t)odds,(int32_t)maxodds);
return(0);
}
CCchange = betTx.vout[0].nValue;
fundsneeded = 2*txfee + (odds-1)*betTx.vout[1].nValue;
if ( (inputs= AddDiceInputs(scriptPubKey,1,cp,mtx,dicepk,fundsneeded,60)) > 0 )
{
if ( inputs > fundsneeded+txfee )
CCchange += (inputs - (fundsneeded+txfee));
mtx.vout.push_back(MakeCC1vout(cp->evalcode,CCchange,dicepk));
mtx.vout.push_back(CTxOut(txfee,entropyTx.vout[1].scriptPubKey));
mtx.vout.push_back(CTxOut(odds * betTx.vout[1].nValue,betTx.vout[2].scriptPubKey));
}
else
{
fprintf(stderr,"not enough inputs for %.8f\n",(double)fundsneeded/COIN);
return(0);
}
}
else
{
funcid = 'L';
mtx.vout.push_back(MakeCC1vout(cp->evalcode,betTx.vout[0].nValue + betTx.vout[1].nValue - txfee,dicepk));
mtx.vout.push_back(CTxOut(txfee,entropyTx.vout[1].scriptPubKey));
}
hentropy = DiceHashEntropy(entropy,mtx.vin[0].prevout.hash);
return(FinalizeCCTx(0,cp,mtx,mypk,txfee,EncodeDiceOpRet(funcid,sbits,fundingtxid,hentropy)));
} else fprintf(stderr,"iswin.%d does not match.%d\n",iswin,winloserefund); } else fprintf(stderr,"iswin.%d does not match.%d\n",iswin,winloserefund);
} else return(0); } else return(0);
} }
return(0); return(0);
} }
std::string DiceLoser(uint64_t txfee,char *planstr,uint256 fundingtxid,uint256 bettxid)
{
CMutableTransaction mtx; uint256 entropy,hentropy; CPubKey mypk,dicepk; uint64_t sbits; struct CCcontract_info *cp,C; int64_t minbet,maxbet,maxodds,timeoutblocks; uint64_t amount = 0;
if ( amount < 0 )
{
fprintf(stderr,"negative parameter error\n");
return(0);
}
if ( (cp= Diceinit(fundingtxid,&C,planstr,txfee,mypk,dicepk,sbits,minbet,maxbet,maxodds,timeoutblocks)) == 0 )
return(0);
if ( AddNormalinputs(mtx,mypk,amount+2*txfee,64) > 0 )
{
hentropy = DiceHashEntropy(entropy,mtx.vin[0].prevout.hash);
mtx.vout.push_back(MakeCC1vout(cp->evalcode,amount,dicepk));
mtx.vout.push_back(CTxOut(txfee,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG));
return(FinalizeCCTx(0,cp,mtx,mypk,txfee,EncodeDiceOpRet('W',sbits,fundingtxid,hentropy)));
} else fprintf(stderr,"cant find enough inputs\n");
fprintf(stderr,"cant find fundingtxid\n");
return(0);
}
std::string DiceRefund(uint64_t txfee,char *planstr,uint256 fundingtxid,uint256 bettxid)
{
CMutableTransaction mtx; uint256 entropy,hentropy; CPubKey mypk,dicepk; uint64_t sbits; struct CCcontract_info *cp,C; int64_t minbet,maxbet,maxodds,timeoutblocks; uint64_t amount = 0;
if ( amount < 0 )
{
fprintf(stderr,"negative parameter error\n");
return(0);
}
if ( (cp= Diceinit(fundingtxid,&C,planstr,txfee,mypk,dicepk,sbits,minbet,maxbet,maxodds,timeoutblocks)) == 0 )
return(0);
if ( AddNormalinputs(mtx,mypk,amount+2*txfee,64) > 0 )
{
hentropy = DiceHashEntropy(entropy,mtx.vin[0].prevout.hash);
mtx.vout.push_back(MakeCC1vout(cp->evalcode,amount,dicepk));
mtx.vout.push_back(CTxOut(txfee,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG));
return(FinalizeCCTx(0,cp,mtx,mypk,txfee,EncodeDiceOpRet('W',sbits,fundingtxid,hentropy)));
} else fprintf(stderr,"cant find enough inputs\n");
fprintf(stderr,"cant find fundingtxid\n");
return(0);
}