Browse Source

Merge pull request #822 from jl777/jl777

Reduce AC notarization frequency
pass-iguana-arg
jl777 7 years ago
committed by GitHub
parent
commit
12ad3648ad
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      crypto777/bitcoind_RPC.c
  2. 6
      iguana/SuperNET_keys.c
  3. 2
      iguana/dPoW.h
  4. 51
      iguana/dpow/dpow_network.c
  5. 2
      iguana/dpow/dpow_rpc.c
  6. 2
      iguana/exchanges/prices/autoprice
  7. 2
      iguana/exchanges/supernet
  8. 14
      iguana/iguana_notary.c
  9. 2
      iguana/iguana_wallet.c

9
crypto777/bitcoind_RPC.c

@ -80,13 +80,13 @@ char *post_process_bitcoind_RPC(char *debugstr,char *command,char *rpcstr,char *
if ( command == 0 || rpcstr == 0 || rpcstr[0] == 0 )
{
if ( strcmp(command,"signrawtransaction") != 0 && strcmp(command,"getrawtransaction") != 0 )
printf("<<<<<<<<<<< A bitcoind_RPC: %s post_process_bitcoind_RPC.%s.[%s]\n",debugstr,command,params);
printf("<<<<<<<<<<< A bitcoind_RPC: %s post_process_bitcoind_RPC.%s\n",debugstr,command);
return(rpcstr);
}
json = cJSON_Parse(rpcstr);
if ( json == 0 )
{
printf("<<<<<<<<<<< B bitcoind_RPC: %s post_process_bitcoind_RPC.%s can't parse.(%s) params.(%s)\n",debugstr,command,rpcstr,params);
printf("<<<<<<<<<<< B bitcoind_RPC: %s post_process_bitcoind_RPC.%s can't parse.(%s)\n",debugstr,command,rpcstr);
free(rpcstr);
return(0);
}
@ -143,14 +143,15 @@ char *Jay_NXTrequest(char *command,char *params)
char *bitcoind_RPC(char **retstrp,char *debugstr,char *url,char *userpass,char *command,char *params,int32_t timeout)
{
static int didinit,count,count2; static double elapsedsum,elapsedsum2; extern int32_t USE_JAY;
CURL *curl_handle; static int didinit,count,count2; static double elapsedsum,elapsedsum2; extern int32_t USE_JAY;
struct MemoryStruct chunk;
struct curl_slist *headers = NULL; struct return_string s; CURLcode res; CURL *curl_handle;
struct curl_slist *headers = NULL; struct return_string s; CURLcode res;
char *bracket0,*bracket1,*retstr,*databuf = 0; long len; int32_t specialcase,numretries; double starttime;
if ( didinit == 0 )
{
didinit = 1;
curl_global_init(CURL_GLOBAL_ALL); //init the curl session
//curl_handle = curl_easy_init();
}
if ( (0) && (USE_JAY != 0 && (strncmp(url,"http://127.0.0.1:7876/nxt",strlen("http://127.0.0.1:7876/nxt")) == 0 || strncmp(url,"https://127.0.0.1:7876/nxt",strlen("https://127.0.0.1:7876/nxt")) == 0)) )
{

6
iguana/SuperNET_keys.c

@ -339,7 +339,7 @@ int32_t iguana_wifstr_valid(char *wifstr)
return(0);
if ( A > 5*a || a > 5*A || a > n*20 || A > n*20 ) // unlikely it is a real wif
{
printf("reject wif %s due to n.%d a.%d A.%d (%d %d %d %d)\n",wifstr,n,a,A,A > 5*a,a < 5*A,a > n*20,A > n*20);
//printf("reject wif %s due to n.%d a.%d A.%d (%d %d %d %d)\n",wifstr,n,a,A,A > 5*a,a < 5*A,a > n*20,A > n*20);
return(0);
}
bitcoin_wif2priv(&wiftype,&privkey,wifstr);
@ -355,9 +355,9 @@ int32_t iguana_wifstr_valid(char *wifstr)
bitcoin_priv2wiflong(cmpstr2,privkey,wiftype);
if ( bits256_cmp(privkey,cmpkey) == 0 )
return(1);
char str[65],str2[65]; printf("mismatched wifstr %s -> %s -> %s %s %s\n",wifstr,bits256_str(str,privkey),cmpstr,bits256_str(str2,cmpkey),cmpstr2);
// char str[65],str2[65]; printf("mismatched wifstr %s -> %s -> %s %s %s\n",wifstr,bits256_str(str,privkey),cmpstr,bits256_str(str2,cmpkey),cmpstr2);
}
char str[65]; printf("%s is not a wif, privkey.%s\n",wifstr,bits256_str(str,privkey));
//char str[65]; printf("%s is not a wif, privkey.%s\n",wifstr,bits256_str(str,privkey));
return(0);
}

2
iguana/dPoW.h

@ -141,7 +141,7 @@ struct dpow_info
struct dpow_hashheight approved[DPOW_FIFOSIZE],notarized[DPOW_FIFOSIZE];
bits256 activehash,lastnotarized,srctx[DPOW_MAXTX],desttx[DPOW_MAXTX];
uint32_t SRCREALTIME,lastsrcupdate,destupdated,srcconfirms,numdesttx,numsrctx,lastsplit,cancelratify;
int32_t lastheight,maxblocks,SRCHEIGHT,SHORTFLAG,ratifying,minsigs,freq;
int32_t lastheight,maxblocks,SRCHEIGHT,DESTHEIGHT,prevDESTHEIGHT,SHORTFLAG,ratifying,minsigs,freq;
struct pax_transaction *PAX;
portable_mutex_t paxmutex,dexmutex;
uint32_t ipbits[128],numipbits;

51
iguana/dpow/dpow_network.c

@ -1464,11 +1464,32 @@ void dpow_nanomsginit(struct supernet_info *myinfo,char *ipaddr)
void dpow_bestconsensus(struct dpow_info *dp,struct dpow_block *bp)
{
int8_t bestks[64]; int32_t counts[64],i,j,numcrcs=0,numdiff,besti,bestmatches = 0,matches = 0; uint64_t masks[64],matchesmask,recvmask; uint32_t crcval=0; char srcaddr[64],destaddr[64];
int8_t bestks[64]; uint32_t sortbuf[64],wts[64],owts[64],counts[64]; int32_t i,j,median,numcrcs=0,numdiff,besti,bestmatches = 0,matches = 0; uint64_t masks[64],matchesmask,recvmask,topmask; uint32_t crcval=0; char srcaddr[64],destaddr[64];
memset(wts,0,sizeof(wts));
memset(owts,0,sizeof(owts));
for (i=0; i<bp->numnotaries; i++)
{
recvmask = bp->notaries[i].recvmask;
wts[i] = bitweight(recvmask);
for (j=0; j<bp->numnotaries; j++)
if ( ((1LL << j) & recvmask) != 0 )
owts[j]++;
}
topmask = 0xffffffffffffffffLL;
recvmask = 0;
for (i=0; i<bp->numnotaries; i++)
sortbuf[i] = (wts[i] * owts[i]);
revsort32(sortbuf,bp->numnotaries,sizeof(*sortbuf));
median = sortbuf[bp->numnotaries / 2];
if ( ((bp->height / dp->freq) % 10) == 0 )
{
for (i=0; i<bp->numnotaries; i++)
if ( wts[i]*owts[i] < median )
topmask &= ~(1LL << i);
}
memset(masks,0,sizeof(masks));
memset(bestks,0xff,sizeof(bestks));
memset(counts,0,sizeof(counts));
recvmask = 0;
for (numdiff=i=0; i<bp->numnotaries; i++)
{
if ( bits256_nonz(bp->notaries[i].src.prev_hash) != 0 && bits256_nonz(bp->notaries[i].dest.prev_hash) != 0 )
@ -1517,7 +1538,15 @@ void dpow_bestconsensus(struct dpow_info *dp,struct dpow_block *bp)
bp->notaries[bp->myind].bestmask = bp->bestmask = masks[besti];
bp->notaries[bp->myind].bestk = bp->bestk = bestks[besti];
if ( bp->myind == 0 )
printf("%s.%d set matches.%d best.%d to (%d %llx) recv.%llx\n",dp->symbol,bp->height,bp->matches,bp->bestmatches,bp->bestk,(long long)bp->bestmask,(long long)recvmask);
{
for (i=0; i<bp->numnotaries; i++)
printf("%d:%d%s ",wts[i],owts[i],wts[i]*owts[i]>median?"*":"");
printf("median.%d %s.%d set matches.%d best.%d to (%d %llx) recv.%llx topmask.%llx\n",sortbuf[bp->numnotaries/2],dp->symbol,bp->height,bp->matches,bp->bestmatches,bp->bestk,(long long)bp->bestmask,(long long)recvmask,(long long)topmask);
for (i=0; i<bp->numnotaries; i++)
if ( wts[i] == 0 || owts[i] == 0 )
printf("%s.%d:%d ",Notaries_elected[i][0],wts[i],owts[i]);
printf(" <- problem nodes\n");
}
}
bp->recvmask |= recvmask;
if ( bp->bestmask == 0 )//|| (time(NULL) / 180) != bp->lastepoch )
@ -1900,7 +1929,7 @@ void dpow_notarize_update(struct supernet_info *myinfo,struct dpow_info *dp,stru
}
else if ( i != bp->myind && i == senderind && ((1LL << bp->myind) & bp->bestmask) != 0 && ((1LL << i) & bp->bestmask) != 0 && ((1LL << bp->myind) & bp->notaries[i].recvmask) == 0 )
{
if ( now > bp->lastnanosend )
if ( now > bp->lastnanosend+1 )
flag = senderind;
}
if ( 0 && bp->myind <= 1 && bp->notaries[i].paxwdcrc != 0 )
@ -1923,17 +1952,17 @@ void dpow_notarize_update(struct supernet_info *myinfo,struct dpow_info *dp,stru
printf("new PENDING BESTK (%d %llx) state.%d\n",bp->bestk,(long long)bp->bestmask,bp->state);
bp->pendingbestk = bp->bestk;
bp->pendingbestmask = bp->bestmask;
dpow_signedtxgen(myinfo,dp,bp->destcoin,bp,bp->bestk,bp->bestmask,bp->myind,DPOW_SIGBTCCHANNEL,1,0);
dpow_signedtxgen(myinfo,dp,bp->destcoin,bp,bp->pendingbestk,bp->pendingbestmask,bp->myind,DPOW_SIGBTCCHANNEL,1,0);
//printf("finished signing\n");
}
if ( bp->destsigsmasks[bp->bestk] == bp->bestmask ) // have all sigs
if ( bp->destsigsmasks[bp->pendingbestk] == bp->pendingbestmask ) // have all sigs
{
if ( bp->state < 1000 )
dpow_sigscheck(myinfo,dp,bp,bp->myind,1,bp->bestk,bp->bestmask,0,0);
if ( bp->srcsigsmasks[bp->bestk] == bp->bestmask ) // have all sigs
dpow_sigscheck(myinfo,dp,bp,bp->myind,1,bp->pendingbestk,bp->pendingbestmask,0,0);
if ( bp->srcsigsmasks[bp->pendingbestk] == bp->pendingbestmask ) // have all sigs
{
if ( bp->state != 0xffffffff )
dpow_sigscheck(myinfo,dp,bp,bp->myind,0,bp->bestk,bp->bestmask,0,0);
dpow_sigscheck(myinfo,dp,bp,bp->myind,0,bp->pendingbestk,bp->pendingbestmask,0,0);
} //else printf("srcmask.%llx != bestmask.%llx\n",(long long)bp->srcsigsmasks[bp->bestk],(long long)bp->bestmask);
} //else printf("destmask.%llx != bestmask.%llx\n",(long long)bp->destsigsmasks[bp->bestk],(long long)bp->bestmask);
}
@ -1983,7 +2012,7 @@ void dpow_nanoutxoget(struct supernet_info *myinfo,struct dpow_info *dp,struct d
}
}
}
if ( bp->myind == 0 && dispflag != 0 )
if ( 0 && bp->myind == 0 && dispflag != 0 )
{
printf("%s.%d RECV.%-2d %llx (%2d %llx) %llx/%llx matches.%-2d best.%-2d %s\n",dp->symbol,bp->height,senderind,(long long)np->recvmask,(int8_t)np->bestk,(long long)np->bestmask,(long long)np->srcutxo.txid,(long long)np->destutxo.txid,matches,bestmatches,Notaries_elected[senderind][0]);
}
@ -2067,7 +2096,7 @@ void dpow_send(struct supernet_info *myinfo,struct dpow_info *dp,struct dpow_blo
printf("maxiters expired for signed_nn_send dpowsock.%d\n",myinfo->dpowsock);
//portable_mutex_unlock(&myinfo->dpowmutex);
free(np);
if ( bp->myind == 0 )
if ( 0 && bp->myind == 0 )
printf("%d NANOSEND.%d %s.%d channel.%08x (%d) pax.%08x datalen.%d (%d %llx) (%d %llx) recv.%llx\n",i,sentbytes,dp->symbol,np->height,np->channel,size,np->notarize.paxwdcrc,datalen,(int8_t)np->notarize.bestk,(long long)np->notarize.bestmask,bp->notaries[bp->myind].bestk,(long long)bp->notaries[bp->myind].bestmask,(long long)bp->recvmask);
}

2
iguana/dpow/dpow_rpc.c

@ -608,7 +608,7 @@ char *dpow_signrawtransaction(struct supernet_info *myinfo,struct iguana_info *c
}
}
retstr = bitcoinrpc_signrawtransaction(myinfo,coin,0,0,rawtx,vins,privkeys,"ALL");
printf("call sign.(%s) vins.(%s) privs.(%s) -> (%s)\n",rawtx,jprint(vins,0),jprint(privkeys,0),retstr);
//printf("call sign.(%s) vins.(%s) privs.(%s) -> (%s)\n",rawtx,jprint(vins,0),jprint(privkeys,0),retstr);
free_json(privkeys);
return(retstr);
}

2
iguana/exchanges/prices/autoprice

@ -39,7 +39,7 @@ sharkholdings="{\"coin\":\"iota\",\"balance\":1500000}, {\"coin\":\"komodo\",\"b
curl --url "http://127.0.0.1:7783" --data "{\"base\":\"MSHARK\",\"rel\":\"KMD\",\"fundvalue_bid\":\"NAV_KMD\",\"fundvalue_ask\":\"assetNAV_KMD\",\"userpass\":\"$userpass\",\"method\":\"autoprice\",\"margin\":$margin,\"address\":\"RTu3JZZKLJTcfNwBa19dWRagEfQq49STqC\",\"holdings\":[$sharkholdings],\"divisor\":1400000}"
curl --url "http://127.0.0.1:7783" --data "{\"margin\":$margin,\"base\":\"SUPERNET\",\"rel\":\"KMD\",\"fundvalue_bid\":\"NAV_KMD\",\"fundvalue_ask\":\"NAV_KMD\",\"userpass\":\"$userpass\",\"method\":\"autoprice\",\"address\":\"RRyyejME7LRTuvdziWsXkAbSW1fdiohGwK\",\"holdings\":[{\"coin\":\"iota\",\"balance\":11000000}, {\"coin\":\"stratis\",\"balance\":1300000}, {\"coin\":\"zcash\",\"balance\":0.10000}, {\"coin\":\"syscoin\",\"balance\":20000000}, {\"coin\":\"waves\",\"balance\":700000}, {\"coin\":\"bitcoin\",\"balance\":570}, {\"coin\":\"bitcoin-cash\",\"balance\":1500}, {\"coin\":\"heat-ledger\",\"balance\":2323851 }, {\"coin\":\"decred\",\"balance\":0.20000}, {\"coin\":\"vericoin\",\"balance\":2199368 }, {\"coin\":\"byteball\",\"balance\":4238}, {\"coin\":\"iocoin\",\"balance\":0.150000}, {\"coin\":\"quantum-resistant-ledger\",\"balance\":0.375000}, {\"coin\":\"chips\",\"balance\":2577006 }, {\"coin\":\"hush\",\"balance\":100000 }, {\"coin\":\"mobilego\",\"balance\":100000 }],\"divisor\":612529}"
curl --url "http://127.0.0.1:7783" --data "{\"margin\":$margin,\"base\":\"SUPERNET\",\"rel\":\"KMD\",\"fundvalue_bid\":\"NAV_KMD\",\"fundvalue_ask\":\"NAV_KMD\",\"userpass\":\"$userpass\",\"method\":\"autoprice\",\"address\":\"RRyyejME7LRTuvdziWsXkAbSW1fdiohGwK\",\"holdings\":[{\"coin\":\"iota\",\"balance\":11000000}, {\"coin\":\"stratis\",\"balance\":1300000}, {\"coin\":\"zcash\",\"balance\":0.10000}, {\"coin\":\"syscoin\",\"balance\":20000000}, {\"coin\":\"waves\",\"balance\":700000}, {\"coin\":\"bitcoin\",\"balance\":570}, {\"coin\":\"bitcoin-cash\",\"balance\":1500}, {\"coin\":\"heat-ledger\",\"balance\":2323851 }, {\"coin\":\"decred\",\"balance\":0.20000}, {\"coin\":\"vericoin\",\"balance\":2199368 }, {\"coin\":\"byteball\",\"balance\":4238}, {\"coin\":\"iocoin\",\"balance\":0.150000}, {\"coin\":\"quantum-resistant-ledger\",\"balance\":0.375000}, {\"coin\":\"chips\",\"balance\":2577006 }, {\"coin\":\"hush\",\"balance\":100000 }, {\"coin\":\"mobilego\",\"balance\":100000 }, {\"coin\":\"utrum\",\"balance\":2100000}],\"divisor\":612529}"
curl --url "http://127.0.0.1:7783" --data "{\"margin\":$margin,\"base\":\"HODL\",\"rel\":\"KMD\",\"fundvalue_bid\":\"assetNAV_KMD\",\"fundvalue_ask\":\"assetNAV_KMD\",\"userpass\":\"$userpass\",\"method\":\"autoprice\",\"address\":\"RNcUaMUEFLxVwtTo7rgruhwYanGk1jTkeU\",\"holdings\":[{\"coin\":\"siacoin\",\"balance\":185000000,\"comment\":\"using siafunds equal to million siacoin\"}],\"divisor\":10000000}"

2
iguana/exchanges/supernet

@ -15,5 +15,5 @@ echo supernet
curl --url "http://127.0.0.1:7783" --data "{\"userpass\":\"$userpass\",\"method\":\"balances\",\"address\":\"RRyyejME7LRTuvdziWsXkAbSW1fdiohGwK\"}"
echo supernet
curl --url "http://127.0.0.1:7783" --data "{\"userpass\":\"$userpass\",\"method\":\"fundvalue\",\"address\":\"RRyyejME7LRTuvdziWsXkAbSW1fdiohGwK\",\"holdings\":[{\"coin\":\"iota\",\"balance\":11000000}, {\"coin\":\"stratis\",\"balance\":1300000}, {\"coin\":\"zcash\",\"balance\":0.10000}, {\"coin\":\"syscoin\",\"balance\":20000000}, {\"coin\":\"waves\",\"balance\":700000}, {\"coin\":\"bitcoin\",\"balance\":570}, {\"coin\":\"bitcoin-cash\",\"balance\":1500}, {\"coin\":\"heat-ledger\",\"balance\":2323851 }, {\"coin\":\"decred\",\"balance\":0.20000}, {\"coin\":\"vericoin\",\"balance\":2199368 }, {\"coin\":\"byteball\",\"balance\":4238}, {\"coin\":\"iocoin\",\"balance\":0.150000}, {\"coin\":\"quantum-resistant-ledger\",\"balance\":0.375000}, {\"coin\":\"hush\",\"balance\":100000 }, {\"coin\":\"mobilego\",\"balance\":100000 }],\"divisor\":612529}"
curl --url "http://127.0.0.1:7783" --data "{\"userpass\":\"$userpass\",\"method\":\"fundvalue\",\"address\":\"RRyyejME7LRTuvdziWsXkAbSW1fdiohGwK\",\"holdings\":[{\"coin\":\"iota\",\"balance\":11000000}, {\"coin\":\"stratis\",\"balance\":1300000}, {\"coin\":\"zcash\",\"balance\":0.10000}, {\"coin\":\"syscoin\",\"balance\":20000000}, {\"coin\":\"waves\",\"balance\":700000}, {\"coin\":\"bitcoin\",\"balance\":570}, {\"coin\":\"bitcoin-cash\",\"balance\":1500}, {\"coin\":\"heat-ledger\",\"balance\":2323851 }, {\"coin\":\"decred\",\"balance\":0.20000}, {\"coin\":\"vericoin\",\"balance\":2199368 }, {\"coin\":\"byteball\",\"balance\":4238}, {\"coin\":\"iocoin\",\"balance\":0.150000}, {\"coin\":\"quantum-resistant-ledger\",\"balance\":0.375000}, {\"coin\":\"hush\",\"balance\":100000 }, {\"coin\":\"mobilego\",\"balance\":100000 }, {\"coin\":\"utrum\",\"balance\":2100000}],\"divisor\":612529}"

14
iguana/iguana_notary.c

@ -61,10 +61,18 @@ void dpow_checkpointset(struct supernet_info *myinfo,struct dpow_checkpoint *che
void dpow_srcupdate(struct supernet_info *myinfo,struct dpow_info *dp,int32_t height,bits256 hash,uint32_t timestamp,uint32_t blocktime)
{
//struct komodo_ccdataMoMoM mdata; cJSON *blockjson; uint64_t signedmask; struct iguana_info *coin;
void **ptrs; char str[65]; struct dpow_checkpoint checkpoint; int32_t i,ht; struct dpow_block *bp;
void **ptrs; char str[65]; struct dpow_checkpoint checkpoint; int32_t i,ht,suppress=0; struct dpow_block *bp;
dpow_checkpointset(myinfo,&dp->last,height,hash,timestamp,blocktime);
checkpoint = dp->srcfifo[dp->srcconfirms];
dpow_fifoupdate(myinfo,dp->srcfifo,dp->last);
if ( strcmp(dp->dest,"KMD") == 0 )
{
if ( dp->DESTHEIGHT < dp->prevDESTHEIGHT+DPOW_CHECKPOINTFREQ )
{
suppress = 1;
fprintf(stderr,"suppress %s -> KMD\n",dp->symbol);
}
}
/*if ( strcmp(dp->dest,"KMD") == 0 )//|| strcmp(dp->dest,"CHAIN") == 0 )
{
//if ( dp->SRCREALTIME == 0 )
@ -109,11 +117,12 @@ void dpow_srcupdate(struct supernet_info *myinfo,struct dpow_info *dp,int32_t he
}*/
if ( dp->freq <= 0 )
dp->freq = 1;
if ( bits256_nonz(checkpoint.blockhash.hash) != 0 && (checkpoint.blockhash.height % dp->freq) == 0 )
if ( suppress == 0 && bits256_nonz(checkpoint.blockhash.hash) != 0 && (checkpoint.blockhash.height % dp->freq) == 0 )
{
if ( (0) && strcmp("KMD",dp->symbol) == 0 )
printf("%s/%s src ht.%d dest.%u nonz.%d %s minsigs.%d freq.%d\n",dp->symbol,dp->dest,checkpoint.blockhash.height,dp->destupdated,bits256_nonz(checkpoint.blockhash.hash),bits256_str(str,dp->last.blockhash.hash),dp->minsigs,dp->freq);
dpow_heightfind(myinfo,dp,checkpoint.blockhash.height + 1000);
dp->prevDESTHEIGHT = dp->DESTHEIGHT;
ptrs = calloc(1,sizeof(void *)*5 + sizeof(struct dpow_checkpoint) + sizeof(pthread_t));
ptrs[0] = (void *)myinfo;
ptrs[1] = (void *)dp;
@ -184,6 +193,7 @@ void dpow_destconfirm(struct supernet_info *myinfo,struct dpow_info *dp,struct d
void dpow_destupdate(struct supernet_info *myinfo,struct dpow_info *dp,int32_t height,bits256 hash,uint32_t timestamp,uint32_t blocktime)
{
dp->destupdated = timestamp;
dp->DESTHEIGHT = height;
dpow_checkpointset(myinfo,&dp->destchaintip,height,hash,timestamp,blocktime);
dpow_approvedset(myinfo,dp,&dp->destchaintip,dp->desttx,dp->numdesttx);
dpow_fifoupdate(myinfo,dp->destfifo,dp->destchaintip);

2
iguana/iguana_wallet.c

@ -980,7 +980,7 @@ cJSON *iguana_privkeysjson(struct supernet_info *myinfo,struct iguana_info *coin
if ( address != 0 )
{
strcpy(&addresses[64 * n++],address);
} else printf("cant get address from.(%s)\n",jprint(item,0));
} //else printf("cant get address from.(%s)\n",jprint(item,0));
}
for (i=0; i<n; i++)
{

Loading…
Cancel
Save