diff --git a/mytoncore/complaints/remove-proofs-v2.fif b/mytoncore/complaints/remove-proofs-v2.fif new file mode 100644 index 00000000..0079dfab --- /dev/null +++ b/mytoncore/complaints/remove-proofs-v2.fif @@ -0,0 +1,90 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Removes proof cells from complaint." cr cr + + ." is a filename of the serialized TL-B ValidatorComplaint boc." cr + ."Saves the result boc into ``." cr 1 halt +} : usage +$# 2 = { cr } { usage } cond + +$1 =: filename +$2 =: savefile + +filename file>B dup +8 B| drop B>$ "b5ee9c72" $= { B>$ x>B? drop } if +B>boc =: complaint + +."got: " cr +complaint ref, + ref, + b> // s, c +} : clear_producer_info + +{ // c + // c +} : clean_descr + +{ // c + // c +} : clean_descr_with_diff + + +// prod_info#34 utime:uint32 mc_blk_ref:ExtBlkRef state_proof:^(MERKLE_PROOF Block) +// prod_proof:^(MERKLE_PROOF ShardState) = ProducerInfo; +// +// no_blk_gen#450e8bd9 from_utime:uint32 prod_info:^ProducerInfo = ComplaintDescr; +// no_blk_gen_diff#c737b0ca prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr; +// +// validator_complaint#bc validator_pubkey:bits256 description:^ComplaintDescr created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams suggested_fine_part:uint32 = ValidatorComplaint; + +complaint =: result_cell + +"result: " type cr +result_cell B +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/mytoncore/functions.py b/mytoncore/functions.py index efa14c4d..2b654dc0 100755 --- a/mytoncore/functions.py +++ b/mytoncore/functions.py @@ -504,13 +504,11 @@ def Complaints(local, ton): # Voting for complaints config32 = ton.GetConfig32() electionId = config32.get("startWorkTime") - complaintsHashes = ton.SaveComplaints(electionId) - complaints = ton.GetComplaints(electionId) - for key, item in complaints.items(): - complaintHash = item.get("hash") - complaintHash_hex = Dec2HexAddr(complaintHash) - if complaintHash_hex in complaintsHashes: - ton.VoteComplaint(electionId, complaintHash) + complaints = ton.GetComplaints(electionId) # get complaints from Elector + for c in complaints.values(): + complaint_hash = c.get("hash") + if ton.complaint_is_valid(c): + ton.VoteComplaint(electionId, complaint_hash) # end define @@ -525,6 +523,10 @@ def Slashing(local, ton): config32 = ton.GetConfig32() start = config32.get("startWorkTime") end = config32.get("endWorkTime") + config15 = ton.GetConfig15() + ts = get_timestamp() + if not(end < ts < end + config15['stakeHeldFor']): # check that currently is freeze time + return local.add_log("slash_time {}, start {}, end {}".format(slash_time, start, end), "debug") if slash_time != start: end -= 60 @@ -560,15 +562,20 @@ def ScanLiteServers(local, ton): def General(local): local.add_log("start General function", "debug") ton = MyTonCore(local) - scanner = Dict() + # scanner = Dict() # scanner.Run() - # Запустить потоки + # Start threads local.start_cycle(Elections, sec=600, args=(local, ton, )) local.start_cycle(Statistics, sec=10, args=(local, )) local.start_cycle(Offers, sec=600, args=(local, ton, )) - local.start_cycle(Complaints, sec=600, args=(local, ton, )) - local.start_cycle(Slashing, sec=600, args=(local, ton, )) + + t = 600 + if ton.GetNetworkName() != 'mainnet': + t = 60 + local.start_cycle(Complaints, sec=t, args=(local, ton, )) + local.start_cycle(Slashing, sec=t, args=(local, ton, )) + local.start_cycle(Domains, sec=600, args=(local, ton, )) local.start_cycle(Telemetry, sec=60, args=(local, ton, )) local.start_cycle(OverlayTelemetry, sec=7200, args=(local, ton, )) diff --git a/mytoncore/mytoncore.py b/mytoncore/mytoncore.py index d98a7ba3..3d18fb6b 100644 --- a/mytoncore/mytoncore.py +++ b/mytoncore/mytoncore.py @@ -533,6 +533,7 @@ def GetVersionFromCodeHash(self, inputHash): arr["hv1"] = "fc8e48ed7f9654ba76757f52cc6031b2214c02fab9e429ffa0340f5575f9f29c" arr["pool"] = "399838da9489139680e90fd237382e96ba771fdf6ea27eb7d513965b355038b4" arr["spool"] = "fc2ae44bcaedfa357d0091769aabbac824e1c28f14cc180c0b52a57d83d29054" + arr["spool_r2"] = "42bea8fea43bf803c652411976eb2981b9bdb10da84eb788a63ea7a01f2a044d" for version, hash in arr.items(): if hash == inputHash: return version @@ -560,7 +561,7 @@ def GetFullConfigAddr(self): result = self.liteClient.Run("getconfig 0") configAddr_hex = self.GetVarFromWorkerOutput(result, "config_addr:x") fullConfigAddr = "-1:{configAddr_hex}".format(configAddr_hex=configAddr_hex) - + # Set buffer self.SetFunctionBuffer(bname, fullConfigAddr) return fullConfigAddr @@ -615,7 +616,7 @@ def GetFullDnsRootAddr(self): result = self.liteClient.Run("getconfig 4") dnsRootAddr_hex = self.GetVarFromWorkerOutput(result, "dns_root_addr:x") fullDnsRootAddr = "-1:{dnsRootAddr_hex}".format(dnsRootAddr_hex=dnsRootAddr_hex) - + # Set buffer self.SetFunctionBuffer(bname, fullDnsRootAddr) return fullDnsRootAddr @@ -636,7 +637,7 @@ def GetActiveElectionId(self, fullElectorAddr): activeElectionId = activeElectionId.replace(' ', '') activeElectionId = parse(activeElectionId, '[', ']') activeElectionId = int(activeElectionId) - + # Set buffer self.SetFunctionBuffer(bname, activeElectionId) return activeElectionId @@ -865,7 +866,7 @@ def GetConfig(self, configId): start = result.find("ConfigParam") text = result[start:] data = self.Tlb2Json(text) - + # Set buffer self.SetFunctionBuffer(bname, data) return data @@ -910,9 +911,9 @@ def GetConfig32(self): if "public_key:" in line: validatorAdnlAddr = parse(line, "adnl_addr:x", ')') pubkey = parse(line, "pubkey:x", ')') - if config32["totalValidators"] > 1: + try: validatorWeight = int(parse(line, "weight:", ' ')) - else: + except ValueError: validatorWeight = int(parse(line, "weight:", ')')) buff = dict() buff["adnlAddr"] = validatorAdnlAddr @@ -920,7 +921,7 @@ def GetConfig32(self): buff["weight"] = validatorWeight validators.append(buff) config32["validators"] = validators - + # Set buffer self.SetFunctionBuffer(bname, config32) return config32 @@ -947,9 +948,9 @@ def GetConfig34(self): if "public_key:" in line: validatorAdnlAddr = parse(line, "adnl_addr:x", ')') pubkey = parse(line, "pubkey:x", ')') - if config34["totalValidators"] > 1: + try: validatorWeight = int(parse(line, "weight:", ' ')) - else: + except ValueError: validatorWeight = int(parse(line, "weight:", ')')) buff = dict() buff["adnlAddr"] = validatorAdnlAddr @@ -957,7 +958,7 @@ def GetConfig34(self): buff["weight"] = validatorWeight validators.append(buff) config34["validators"] = validators - + # Set buffer self.SetFunctionBuffer(bname, config34) return config34 @@ -994,7 +995,7 @@ def GetConfig36(self): except: config36["validators"] = list() #end try - + # Set buffer self.SetFunctionBuffer(bname, config36) return config36 @@ -1085,7 +1086,7 @@ def CreateConfigProposalRequest(self, offerHash, validatorIndex): return var1 #end define - def CreateComplaintRequest(self, electionId , complaintHash, validatorIndex): + def CreateComplaintRequest(self, electionId, complaintHash, validatorIndex): self.local.add_log("start CreateComplaintRequest function", "debug") fileName = self.tempDir + "complaint_validator-to-sign.req" args = ["complaint-vote-req.fif", validatorIndex, electionId, complaintHash, fileName] @@ -1103,6 +1104,15 @@ def CreateComplaintRequest(self, electionId , complaintHash, validatorIndex): return var1 #end define + def remove_proofs_from_complaint(self, input_file_name: str): + self.local.add_log("start remove_proofs_from_complaint function", "debug") + output_file_name = self.tempDir + "complaint-new.boc" + fift_script = pkg_resources.resource_filename('mytoncore', 'complaints/remove-proofs-v2.fif') + args = [fift_script, input_file_name, output_file_name] + result = self.fift.Run(args) + return output_file_name + + def PrepareComplaint(self, electionId, inputFileName): self.local.add_log("start PrepareComplaint function", "debug") fileName = self.tempDir + "complaint-msg-body.boc" @@ -1160,7 +1170,7 @@ def SignBocWithWallet(self, wallet, boc_path, dest, coins, **kwargs): if account.balance < coins + 0.1: raise Exception("Wallet balance is less than requested coins") #end if - + # Bounceable checking destAccount = self.GetAccount(dest) bounceable = self.IsBounceableAddrB64(dest) @@ -1195,15 +1205,19 @@ def SendFile(self, filePath, wallet=None, **kwargs): timeout = kwargs.get("timeout", 30) remove = kwargs.get("remove", True) duplicateSendfile = self.local.db.get("duplicateSendfile", True) + duplicateApi = self.local.db.get("duplicateApi", False) if not os.path.isfile(filePath): raise Exception("SendFile error: no such file '{filePath}'".format(filePath=filePath)) if timeout and wallet: wallet.oldseqno = self.GetSeqno(wallet) self.liteClient.Run("sendfile " + filePath) if duplicateSendfile: + try: + self.liteClient.Run("sendfile " + filePath, useLocalLiteServer=False) + self.liteClient.Run("sendfile " + filePath, useLocalLiteServer=False) + except: pass + if duplicateApi: self.send_boc_toncenter(filePath) - # self.liteClient.Run("sendfile " + filePath, useLocalLiteServer=False) - # self.liteClient.Run("sendfile " + filePath, useLocalLiteServer=False) if timeout and wallet: self.WaitTransaction(wallet, timeout) if remove == True: @@ -1218,10 +1232,13 @@ def send_boc_toncenter(self, file_path: str): data = {"boc": boc_b64} network_name = self.GetNetworkName() if network_name == 'testnet': - url = 'https://testnet.toncenter.com/api/v2/sendBoc' + default_url = 'https://testnet.toncenter.com/api/v2/sendBoc' elif network_name == 'mainnet': - url = 'https://toncenter.com/api/v2/sendBoc' + default_url = 'https://toncenter.com/api/v2/sendBoc' else: + default_url = None + url = self.local.db.get("duplicateApiUrl", default_url) + if url == None: return False result = requests.post(url=url, json=data) if result.status_code != 200: @@ -1765,7 +1782,7 @@ def GetValidatorConfig(self): vconfig = json.loads(text) return Dict(vconfig) #end define - + def GetOverlaysStats(self): self.local.add_log("start GetOverlaysStats function", "debug") resultFilePath = self.local.buffer.my_temp_dir + "getoverlaysstats.json" @@ -1816,7 +1833,7 @@ def MoveCoins(self, wallet, dest, coins, **kwargs): if account.status != "active": raise Exception("Wallet account is uninitialized") #end if - + # Bounceable checking destAccount = self.GetAccount(dest) bounceable = self.IsBounceableAddrB64(dest) @@ -2339,9 +2356,9 @@ def SaveComplaints(self, electionId): return complaintsHashes #end define - def CheckComplaint(self, filePath): + def CheckComplaint(self, file_path: str): self.local.add_log("start CheckComplaint function", "debug") - cmd = "loadproofcheck {filePath}".format(filePath=filePath) + cmd = "loadproofcheck {filePath}".format(filePath=file_path) result = self.liteClient.Run(cmd, timeout=30) lines = result.split('\n') ok = False @@ -2355,6 +2372,52 @@ def CheckComplaint(self, filePath): return ok #end define + def complaint_is_valid(self, complaint: dict): + self.local.add_log("start complaint_is_valid function", "debug") + + voted_complaints = self.GetVotedComplaints() + if complaint['pseudohash'] in voted_complaints: + self.local.add_log(f"skip checking complaint {complaint['hash']}: " + f"complaint with this pseudohash ({complaint['pseudohash']})" + f" has already been voted", "debug") + return False + + # check that complaint is valid + election_id = complaint['electionId'] + config32 = self.GetConfig32() + start = config32.get("startWorkTime") + if election_id != start: + self.local.add_log(f"skip checking complaint {complaint['hash']}: " + f"election_id ({election_id}) doesn't match with " + f"start work time ({config32.get('startWorkTime')})", "info") + return False + end = config32.get("endWorkTime") + data = self.GetValidatorsLoad(start, end - 60, saveCompFiles=False) + + exists = False + for item in data.values(): + pubkey = item.get("pubkey") + if pubkey is None: + continue + pseudohash = pubkey + str(election_id) + if pseudohash == complaint['pseudohash']: + exists = True + break + + if not exists: + self.local.add_log(f"complaint {complaint['hash']} declined: complaint info was not found", "info") + return False + + # check complaint fine value + if complaint['suggestedFine'] != 101: # https://github.com/ton-blockchain/ton/blob/5847897b3758bc9ea85af38e7be8fc867e4c133a/lite-client/lite-client.cpp#L3708 + self.local.add_log(f"complaint {complaint['hash']} declined: complaint fine value is {complaint['suggestedFine']} ton", "info") + return False + if complaint['suggestedFinePart'] != 0: # https://github.com/ton-blockchain/ton/blob/5847897b3758bc9ea85af38e7be8fc867e4c133a/lite-client/lite-client.cpp#L3709 + self.local.add_log(f"complaint {complaint['hash']} declined: complaint fine part value is {complaint['suggestedFinePart']} ton", "info") + return False + + return True + def GetOnlineValidators(self): onlineValidators = list() validators = self.GetValidatorsList() @@ -2457,7 +2520,7 @@ def GetValidatorsList(self, past=False): if buff: return buff #end if - + timestamp = get_timestamp() end = timestamp - 60 start = end - 2000 @@ -2482,7 +2545,7 @@ def GetValidatorsList(self, past=False): if saveElectionEntries and adnlAddr in saveElectionEntries: validator["walletAddr"] = saveElectionEntries[adnlAddr]["walletAddr"] #end for - + # Set buffer self.SetFunctionBuffer(bname, validators) return validators @@ -2513,6 +2576,7 @@ def CheckValidators(self, start, end): if pseudohash in complaints: continue # Create complaint + fileName = self.remove_proofs_from_complaint(fileName) fileName = self.PrepareComplaint(electionId, fileName) fileName = self.SignBocWithWallet(wallet, fileName, fullElectorAddr, 300) self.SendFile(fileName, wallet) @@ -2954,7 +3018,7 @@ def ParseAddrB64(self, addrB64): if buff: return buff #end if - + buff = addrB64.replace('-', '+') buff = buff.replace('_', '/') buff = buff.encode() @@ -2988,7 +3052,7 @@ def ParseAddrB64(self, addrB64): workchain = int.from_bytes(workchain_bytes, "big", signed=True) addr = addr_bytes.hex() - + # Set buffer data = (workchain, addr, bounceable) self.SetFunctionBuffer(fname, data) @@ -3015,7 +3079,7 @@ def ParseInputAddr(self, inputAddr): else: raise Exception(f"ParseInputAddr error: input address is not a adress: {inputAddr}") #end define - + def IsBounceableAddrB64(self, inputAddr): bounceable = None try: @@ -3331,7 +3395,7 @@ def CreatePool(self, poolName, validatorRewardSharePercent, maxNominatorsCount, if "Saved pool" not in result: raise Exception("CreatePool error: " + result) #end if - + pools = self.GetPools() newPool = self.GetLocalPool(poolName) for pool in pools: @@ -3362,7 +3426,7 @@ def DepositToPool(self, poolAddr, amount): resultFilePath = self.SignBocWithWallet(wallet, bocPath, poolAddr, amount) self.SendFile(resultFilePath, wallet) #end define - + def WithdrawFromPool(self, poolAddr, amount): poolData = self.GetPoolData(poolAddr) if poolData["state"] == 0: @@ -3381,20 +3445,20 @@ def WithdrawFromPoolProcess(self, poolAddr, amount): resultFilePath = self.SignBocWithWallet(wallet, bocPath, poolAddr, 1.35) self.SendFile(resultFilePath, wallet) #end define - + def PendWithdrawFromPool(self, poolAddr, amount): self.local.add_log("start PendWithdrawFromPool function", "debug") pendingWithdraws = self.GetPendingWithdraws() pendingWithdraws[poolAddr] = amount self.local.save() #end define - + def HandlePendingWithdraw(self, pendingWithdraws, poolAddr): amount = pendingWithdraws.get(poolAddr) self.WithdrawFromPoolProcess(poolAddr, amount) pendingWithdraws.pop(poolAddr) #end define - + def GetPendingWithdraws(self): bname = "pendingWithdraws" pendingWithdraws = self.local.db.get(bname) @@ -3570,7 +3634,7 @@ def create_single_pool(self, pool_name, owner_address): if "Saved single nominator pool" not in result: raise Exception("create_single_pool error: " + result) #end if - + pools = self.GetPools() new_pool = self.GetLocalPool(pool_name) for pool in pools: @@ -3599,7 +3663,7 @@ def GetNetworkName(self): else: return "unknown" #end define - + def GetFunctionBuffer(self, name, timeout=10): timestamp = get_timestamp() buff = self.local.buffer.get(name) @@ -3612,7 +3676,7 @@ def GetFunctionBuffer(self, name, timeout=10): data = buff.get("data") return data #end define - + def SetFunctionBuffer(self, name, data): buff = dict() buff["time"] = get_timestamp() diff --git a/mytonctrl/mytonctrl.py b/mytonctrl/mytonctrl.py index 168a0ebc..06a4d8ac 100755 --- a/mytonctrl/mytonctrl.py +++ b/mytonctrl/mytonctrl.py @@ -901,8 +901,8 @@ def PrintBookmarksList(ton, args): name = item.get("name") type = item.get("type") addr = item.get("addr") - data = item.get("data") - table += [[name, type, addr, data]] + bookmark_data = item.get("data") + table += [[name, type, addr, bookmark_data]] print_table(table) #end define @@ -948,14 +948,17 @@ def DeleteBookmark(ton, args): # #end define def PrintOffersList(ton, args): - offers = ton.GetOffers() + data = ton.GetOffers() + if (data is None or len(data) == 0): + print("No data") + return if "--json" in args: - text = json.dumps(offers, indent=2) + text = json.dumps(data, indent=2) print(text) else: table = list() table += [["Hash", "Votes", "W/L", "Approved", "Is passed"]] - for item in offers: + for item in data: hash = item.get("hash") votedValidators = len(item.get("votedValidators")) wins = item.get("wins") @@ -1007,14 +1010,17 @@ def GetConfig(ton, args): def PrintComplaintsList(ton, args): past = "past" in args - complaints = ton.GetComplaints(past=past) + data = ton.GetComplaints(past=past) + if (data is None or len(data) == 0): + print("No data") + return if "--json" in args: - text = json.dumps(complaints, indent=2) + text = json.dumps(data, indent=2) print(text) else: table = list() table += [["Election id", "ADNL", "Fine (part)", "Votes", "Approved", "Is passed"]] - for key, item in complaints.items(): + for key, item in data.items(): electionId = item.get("electionId") adnl = item.get("adnl") suggestedFine = item.get("suggestedFine") @@ -1117,14 +1123,17 @@ def GetDomainFromAuction(ton, args): def PrintElectionEntriesList(ton, args): past = "past" in args - entries = ton.GetElectionEntries(past=past) + data = ton.GetElectionEntries(past=past) + if (data is None or len(data) == 0): + print("No data") + return if "--json" in args: - text = json.dumps(entries, indent=2) + text = json.dumps(data, indent=2) print(text) else: table = list() table += [["ADNL", "Pubkey", "Wallet", "Stake", "Max-factor"]] - for key, item in entries.items(): + for key, item in data.items(): adnl = item.get("adnlAddr") pubkey = item.get("pubkey") walletAddr = item.get("walletAddr") @@ -1147,14 +1156,17 @@ def VoteElectionEntry(ton, args): def PrintValidatorList(ton, args): past = "past" in args - validators = ton.GetValidatorsList(past=past) + data = ton.GetValidatorsList(past=past) + if (data is None or len(data) == 0): + print("No data") + return if "--json" in args: - text = json.dumps(validators, indent=2) + text = json.dumps(data, indent=2) print(text) else: table = list() table += [["ADNL", "Pubkey", "Wallet", "Efficiency", "Online"]] - for item in validators: + for item in data: adnl = item.get("adnlAddr") pubkey = item.get("pubkey") walletAddr = item.get("walletAddr") diff --git a/mytoninstaller/mytoninstaller.py b/mytoninstaller/mytoninstaller.py index 67306b9c..3400df55 100644 --- a/mytoninstaller/mytoninstaller.py +++ b/mytoninstaller/mytoninstaller.py @@ -181,6 +181,7 @@ def Event(local, name): initBlock_b64 = sys.argv[ix+1] initBlock = b642dict(initBlock_b64) CreateLocalConfig(local, initBlock) + local.exit() #end define diff --git a/setup.py b/setup.py index 21363e7d..26b16bd4 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,10 @@ ], package_data={ 'mytoninstaller.scripts': ['*.sh'], - 'mytoncore.contracts' :['single-nominator-pool/*'], + 'mytoncore': [ + 'contracts/*', + 'complaints/*' + ], 'mytonctrl': [ 'resources/*', 'scripts/*',