Pages:
Author

Topic: [Contest] - Win 2 BTC for the best retargeting algorithm. Python testbed inside! - page 2. (Read 3020 times)

copper member
Activity: 2324
Merit: 1348
newbie
Activity: 34
Merit: 0
Actually, we can improve it even more if we use a very big value for kimoto. It is good according to your test because we update the difficulty at every block.

Code:
import datetime
import random
import numpy as np
import matplotlib.pyplot as plt

# sudo apt-get install python-tk
# pip2 install numpy matplotlib


class RetargetTest():
    # experiment with the number of work packages
    def __init__(self):
        self.current_time = datetime.datetime.now()
        self.works_to_create = 3
        self.generate_blocks = 100
        self.current_height = 0
        self.blockchain = []
        self.work_packages = []
        self.base_target = 0x000000ffffffffffffffffffffffffff
        self.stretch_number_pows = True
        self.do_not_randomize_block_times_but_do_always_60_sec = True
        self.new_miner_every_xth_second = 10
        self.how_many_miners_come_or_go = 70242
        self.initial_miners = 50
        self.miners_kick_in_at_block=50
        self.miners_drop_at = 0
        self.miners_drop_for=20
        self.jitter_size = 0

    def seeder(self, hasher):
        random.seed(hasher)

    def randomize_params(self):
        self.generate_blocks = random.randint(80,500)
        self.new_miner_every_xth_second = random.randint(5,50)
        self.how_many_miners_come_or_go = random.randint(0,1000000)
        self.initial_miners = random.randint(0,1000000)
        self.miners_kick_in_at_block = random.randint(0,40)
        self.jitter_size = random.randint(1,7)
        self.miners_drop_at = random.randint(self.generate_blocks/2,self.generate_blocks)
        pass

    def create_block(self,timestamp, num_pow):
        return {'time_stamp' : timestamp, 'num_pow' : num_pow, 'first_work_factor':0}

    def create_work(self,idx, factor, target):
        return {'id': idx, 'base_executions_per_second' : factor, 'target' : target}

    def addSecs(self,tm, secs):
        fulldate = tm + datetime.timedelta(seconds=secs)
        return fulldate

    def randomDuration(self):
        if self.do_not_randomize_block_times_but_do_always_60_sec:
            return 60
        else:
            return int(random.uniform(25, 120))

    def currently_active_miners(self,current_height):
        if self.current_height            return 0

        if self.current_height>=self.miners_drop_at and self.current_height<=self.miners_drop_at+self.miners_drop_for:
            return 0
        # get the current active number of miners in relation of blockchain height,
        # but the number of miners increases by 1 every 10 blocks
        increases = int(self.current_height/self.new_miner_every_xth_second) * self.how_many_miners_come_or_go
        return self.initial_miners+increases

    def miner_pows_based_on_target(self,work, height, dur):
        current_target = work["target"]
        factor = (current_target / self.base_target) * 1.0*dur/60.0
        actual_pow_mined = work["base_executions_per_second"]
        # random jitter
        actual_pow_mined = abs((actual_pow_mined - self.jitter_size) + random.uniform(self.jitter_size,2*self.jitter_size)) * self.currently_active_miners(height)
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        actual_pow_mined = actual_pow_mined *factor
        # rate limit to 20 pows per block
        if actual_pow_mined > 20:
            actual_pow_mined = 20
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        return actual_pow_mined
    def kimoto(self,x):
        return  1 + (0.7084 * pow(((x)/(144)), -1.228));
        
def retarget_work(self,block, x):
        targetI = x["target"]
        pastMass = 0
        counter = 0
        current_block = block
        current_block_timestamp = self.blockchain[current_block]["time_stamp"]
        adjustment = 0
        isFull = True
        fullCnt = 0
        isEmpty = True
        max_block_reading = 144
        emptyCnt = 0
        while isFull or isEmpty:
            if isFull and self.blockchain[current_block]["num_pow"][x["id"]] == 20:
                fullCnt += 1
            else:
                isFull = False
            if isEmpty and self.blockchain[current_block]["num_pow"][x["id"]] == 0:
                emptyCnt += 1
            else:
                isEmpty = False
            current_block -= 1
            if current_block < 1:
                break
        current_block = block
        while True:
            counter += 1
            pastMass += self.blockchain[current_block]["num_pow"][x["id"]]
            if current_block_timestamp < self.blockchain[current_block-1]["time_stamp"]:
                current_block_timestamp = self.blockchain[current_block-1]["time_stamp"]
            seconds_passed = (current_block_timestamp - self.blockchain[current_block-1]["time_stamp"]).seconds
            current_block -= 1
            if seconds_passed < 1:
                seconds_passed = 1
            trs_per_second = float(pastMass) / float(seconds_passed)
            target_per_second = 10.0 / 60.0
            if trs_per_second > 0:
                adjustment = target_per_second / trs_per_second
                kim = self.kimoto(pastMass * 1000)
                if adjustment > kim or adjustment < (1.0/kim):
                    break
            else:
                adjustment = 1
            if current_block < 1 or counter == max_block_reading:
                break

        if fullCnt > 1:
            adjustment = adjustment / (1 << fullCnt)
        if emptyCnt > 1:
            adjustment = adjustment * (2 << emptyCnt)
        targetI = targetI * adjustment
        if targetI>self.base_target:
                targetI = self.base_target
        if x["id"] == 0:
                self.blockchain[block]["first_work_factor"] = adjustment
        x["target"] = targetI
        #print "Retarget using",counter,"blocks","fullcnt",fullCnt,"emptyCnt",emptyCnt



    def retarget_works(self,block):
        for x in self.work_packages:
            self.retarget_work(block,x)


    def reset_chain(self):
        self.blockchain = []
        self.work_packages = []
        self.current_height = 0
        self.current_time = datetime.datetime.now()

        # Here we create up to three different work objects
        if self.works_to_create>=1:
            self.work_packages.append(self.create_work(0, 20, self.base_target))
        if self.works_to_create>=2:
            self.work_packages.append(self.create_work(1, 60, self.base_target))
        if self.works_to_create>=3:
            self.work_packages.append(self.create_work(2, 35, self.base_target))

    def generate_chain(self):
        while self.current_height < self.generate_blocks:
            if self.current_height%1000==0:
                print "  -> generated block",self.current_height
            dur = self.randomDuration()
            self.current_time = self.addSecs(self.current_time,dur) # random block generation time
            block_pows = {}
            for x in self.work_packages:
                num_pow = self.miner_pows_based_on_target(x, self.current_height, dur) # mine some POW depending on the current difficulty
                block_pows[x["id"]] = num_pow
            self.blockchain.append(self.create_block(self.current_time, block_pows))
            self.retarget_works(self.current_height) # This retargeting method is the "critical part here"
            self.current_height = self.current_height + 1

    def get_total_error(self):
        values = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
          
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)


            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        #print values
        #print ideal
        total_error = 0
        for x in range(len(values)):
            soll = ideal[x]
            ist = values[x]
            total_error += abs(soll-ist)
        return total_error/len(values)

    def plot(self,run):
        values = []
        target_factors = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
            
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)

            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values

        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        #fig = plt.figure()
        ax0 = plt.subplot(211)
        if self.stretch_number_pows:
            ax0.set_ylabel('POW rate per 60s', color='b')
        else:
            ax0.set_ylabel('POWs per Block', color='b')
        ax0.set_xlabel('Block height')
        ax0.plot(x,y,'-o',x,ideal,'r--')
        values = []
        ideal = []
        target_factors = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
            
            sum_x = 0
            sum_x += x["num_pow"][0]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(10*strech_normalizer)
                else:
                    ideal.append(10)

            #print "sumx",sum_x
            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values
        plt.title('All Works: Total POWs')
        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        ax1 = plt.subplot(212)
        ax1.plot(x,y,'-o',x,ideal,'r--')
        ax1.set_xlabel('Block Height')
        # Make the y-axis label and tick labels match the line color.
        if self.stretch_number_pows:
            ax1.set_ylabel('POW rate per 60s', color='b')
        else:
            ax1.set_ylabel('POWs per Block', color='b')

        for tl in ax1.get_yticklabels():
            tl.set_color('b')



        ax2 = ax1.twinx()
        ax2.set_ylim(0.4, 1.6)
        ax2.bar(x,[x["first_work_factor"] for x in self.blockchain][1:],0.45,color='#deb0b0', alpha=0.2)
        ax2.set_ylabel('Retargeting Factor', color='r')
        for tl in ax2.get_yticklabels():
            tl.set_color('r')
        plt.title('First Work: POWs + Retargeting Factor')

        plt.savefig('render-' + str(run) + '.png')
        plt.close()

Edit: Now max value is 4.71
legendary
Activity: 1260
Merit: 1168
Last minute changes to the testbed are welcome ;-) If you know how to test an even more extreme side case (which an be expected in reality) ... go ahead.

Try here 72
    def kimoto(self,x):
        return  1 + (0.7084 * pow(((x)/(28)), -1.228));


And i see the DK3 elemtenst works with 360 sec...that is for a 6 min chain.


Technically we aim for a "6 second" chain here, while in fact it's not a chain! But we want on average every 6 sec / 1 POW to end up with 10 per block ~ 1min.

@LIMX Dev: Your suggestion reaches an error of 5.05! Looks close so far ;-)



Kimoto28:

Code:
import datetime
import random
import numpy as np
import matplotlib.pyplot as plt

# sudo apt-get install python-tk
# pip2 install numpy matplotlib


class RetargetTest():
    # experiment with the number of work packages
    def __init__(self):
        self.current_time = datetime.datetime.now()
        self.works_to_create = 3
        self.generate_blocks = 100
        self.current_height = 0
        self.blockchain = []
        self.work_packages = []
        self.base_target = 0x000000ffffffffffffffffffffffffff
        self.stretch_number_pows = True
        self.do_not_randomize_block_times_but_do_always_60_sec = True
        self.new_miner_every_xth_second = 10
        self.how_many_miners_come_or_go = 70242
        self.initial_miners = 50
        self.miners_kick_in_at_block=50
        self.miners_drop_at = 0
        self.miners_drop_for=20
        self.jitter_size = 0

    def seeder(self, hasher):
        random.seed(hasher)

    def randomize_params(self):
        self.generate_blocks = random.randint(80,500)
        self.new_miner_every_xth_second = random.randint(5,50)
        self.how_many_miners_come_or_go = random.randint(0,1000000)
        self.initial_miners = random.randint(0,1000000)
        self.miners_kick_in_at_block = random.randint(0,40)
        self.jitter_size = random.randint(1,7)
        self.miners_drop_at = random.randint(self.generate_blocks/2,self.generate_blocks)
        pass

    def create_block(self,timestamp, num_pow):
        return {'time_stamp' : timestamp, 'num_pow' : num_pow, 'first_work_factor':0}

    def create_work(self,idx, factor, target):
        return {'id': idx, 'base_executions_per_second' : factor, 'target' : target}

    def addSecs(self,tm, secs):
        fulldate = tm + datetime.timedelta(seconds=secs)
        return fulldate

    def randomDuration(self):
        if self.do_not_randomize_block_times_but_do_always_60_sec:
            return 60
        else:
            return int(random.uniform(25, 120))

    def currently_active_miners(self,current_height):
        if self.current_height            return 0

        if self.current_height>=self.miners_drop_at and self.current_height<=self.miners_drop_at+self.miners_drop_for:
            return 0
        # get the current active number of miners in relation of blockchain height,
        # but the number of miners increases by 1 every 10 blocks
        increases = int(self.current_height/self.new_miner_every_xth_second) * self.how_many_miners_come_or_go
        return self.initial_miners+increases

    def miner_pows_based_on_target(self,work, height, dur):
        current_target = work["target"]
        factor = (current_target / self.base_target) * 1.0*dur/60.0
        actual_pow_mined = work["base_executions_per_second"]
        # random jitter
        actual_pow_mined = abs((actual_pow_mined - self.jitter_size) + random.uniform(self.jitter_size,2*self.jitter_size)) * self.currently_active_miners(height)
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        actual_pow_mined = actual_pow_mined *factor
        # rate limit to 20 pows per block
        if actual_pow_mined > 20:
            actual_pow_mined = 20
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        return actual_pow_mined
    def kimoto(self,x):
        return  1 + (0.7084 * pow(((x)/(28)), -1.228));
    def retarget_work(self,block, x):
        targetI = x["target"]
        pastMass = 0
        counter = 0
        current_block = block
        current_block_timestamp = self.blockchain[current_block]["time_stamp"]
        adjustment = 0
        isFull = True
        fullCnt = 0
        isEmpty = True
        max_block_reading = 144
        emptyCnt = 0
        while isFull or isEmpty:
            if isFull and self.blockchain[current_block]["num_pow"][x["id"]] == 20:
                fullCnt += 1
            else:
                isFull = False
            if isEmpty and self.blockchain[current_block]["num_pow"][x["id"]] == 0:
                emptyCnt += 1
            else:
                isEmpty = False
            current_block -= 1
            if current_block < 1:
                break
        current_block = block
        while True:
            counter += 1
            pastMass += self.blockchain[current_block]["num_pow"][x["id"]]
            if current_block_timestamp < self.blockchain[current_block-1]["time_stamp"]:
                current_block_timestamp = self.blockchain[current_block-1]["time_stamp"]
            seconds_passed = (current_block_timestamp - self.blockchain[current_block-1]["time_stamp"]).seconds
            current_block -= 1
            if seconds_passed < 1:
                seconds_passed = 1
            trs_per_second = float(pastMass) / float(seconds_passed)
            target_per_second = 10.0 / 60.0
            if trs_per_second > 0:
                adjustment = target_per_second / trs_per_second
                kim = self.kimoto(pastMass * 30)
                if adjustment > kim or adjustment < (1.0/kim):
                    break
            else:
                adjustment = 1
            if current_block < 1 or counter == max_block_reading:
                break

        if fullCnt > 1:
            adjustment = adjustment / (1 << fullCnt)
        if emptyCnt > 1:
            adjustment = adjustment * (1 << emptyCnt)
        targetI = targetI * adjustment
        if targetI>self.base_target:
                targetI = self.base_target
        if x["id"] == 0:
                self.blockchain[block]["first_work_factor"] = adjustment
        x["target"] = targetI
        #print "Retarget using",counter,"blocks","fullcnt",fullCnt,"emptyCnt",emptyCnt


    def retarget_works(self,block):
        for x in self.work_packages:
            self.retarget_work(block,x)


    def reset_chain(self):
        self.blockchain = []
        self.work_packages = []
        self.current_height = 0
        self.current_time = datetime.datetime.now()

        # Here we create up to three different work objects
        if self.works_to_create>=1:
            self.work_packages.append(self.create_work(0, 20, self.base_target))
        if self.works_to_create>=2:
            self.work_packages.append(self.create_work(1, 60, self.base_target))
        if self.works_to_create>=3:
            self.work_packages.append(self.create_work(2, 35, self.base_target))

    def generate_chain(self):
        while self.current_height < self.generate_blocks:
            if self.current_height%1000==0:
                print "  -> generated block",self.current_height
            dur = self.randomDuration()
            self.current_time = self.addSecs(self.current_time,dur) # random block generation time
            block_pows = {}
            for x in self.work_packages:
                num_pow = self.miner_pows_based_on_target(x, self.current_height, dur) # mine some POW depending on the current difficulty
                block_pows[x["id"]] = num_pow
            self.blockchain.append(self.create_block(self.current_time, block_pows))
            self.retarget_works(self.current_height) # This retargeting method is the "critical part here"
            self.current_height = self.current_height + 1

    def get_total_error(self):
        values = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)


            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        #print values
        #print ideal
        total_error = 0
        for x in range(len(values)):
            soll = ideal[x]
            ist = values[x]
            total_error += abs(soll-ist)
        return total_error/len(values)

    def plot(self,run):
        values = []
        target_factors = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)

            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values

        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        #fig = plt.figure()
        ax0 = plt.subplot(211)
        if self.stretch_number_pows:
            ax0.set_ylabel('POW rate per 60s', color='b')
        else:
            ax0.set_ylabel('POWs per Block', color='b')
        ax0.set_xlabel('Block height')
        ax0.plot(x,y,'-o',x,ideal,'r--')
        values = []
        ideal = []
        target_factors = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            sum_x += x["num_pow"][0]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(10*strech_normalizer)
                else:
                    ideal.append(10)

            #print "sumx",sum_x
            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values
        plt.title('All Works: Total POWs')
        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        ax1 = plt.subplot(212)
        ax1.plot(x,y,'-o',x,ideal,'r--')
        ax1.set_xlabel('Block Height')
        # Make the y-axis label and tick labels match the line color.
        if self.stretch_number_pows:
            ax1.set_ylabel('POW rate per 60s', color='b')
        else:
            ax1.set_ylabel('POWs per Block', color='b')

        for tl in ax1.get_yticklabels():
            tl.set_color('b')



        ax2 = ax1.twinx()
        ax2.set_ylim(0.4, 1.6)
        ax2.bar(x,[x["first_work_factor"] for x in self.blockchain][1:],0.45,color='#deb0b0', alpha=0.2)
        ax2.set_ylabel('Retargeting Factor', color='r')
        for tl in ax2.get_yticklabels():
            tl.set_color('r')
        plt.title('First Work: POWs + Retargeting Factor')

        plt.savefig('render-' + str(run) + '.png')
        plt.close()
copper member
Activity: 2324
Merit: 1348
Last minute changes to the testbed are welcome ;-) If you know how to test an even more extreme side case (which an be expected in reality) ... go ahead.

Try here 72
    def kimoto(self,x):
        return  1 + (0.7084 * pow(((x)/(28)), -1.228));


And i see the DK3 elemtenst works with 360 sec...that is for a 6 min chain.
legendary
Activity: 1260
Merit: 1168
Last minute changes to the testbed are welcome ;-) If you know how to test an even more extreme side case (which an be expected in reality) ... go ahead.
legendary
Activity: 1260
Merit: 1168
And the plots for the first run of the testbed:

loracle:



verynewmember:



myself:

legendary
Activity: 1260
Merit: 1168
So far, with the dummy seed:

Errors:
loracle: 6.15
verynewmember: 5.73
my own submission (post #13): 4.95

Seriously, my submission is shit! I dont wanna win this!

He again my shitty test case from from a few days ago:

tester.py

Code:
from retarget import RetargetTest

retarget = RetargetTest()
max_error = 0
retarget.seeder("BLOCK_HASH_OF_FIRST_BLOCK_AFTER_CONTEST_GOES HERE")
for i in range(9):
print "Executing run",i
retarget.reset_chain()
retarget.randomize_params()
retarget.generate_chain()
err = retarget.get_total_error()
retarget.plot(i)
print "Run",i,"-","total error =",err
if max_error max_error=err
print "Largest error:",max_error

retarget.py

Code:
import datetime
import random
import numpy as np
import matplotlib.pyplot as plt

# sudo apt-get install python-tk
# pip2 install numpy matplotlib


class RetargetTest():
    # experiment with the number of work packages
    def __init__(self):
        self.current_time = datetime.datetime.now()
        self.works_to_create = 3
        self.generate_blocks = 100
        self.current_height = 0
        self.blockchain = []
        self.work_packages = []
        self.base_target = 0x000000ffffffffffffffffffffffffff
        self.stretch_number_pows = True
        self.do_not_randomize_block_times_but_do_always_60_sec = True
        self.new_miner_every_xth_second = 10
        self.how_many_miners_come_or_go = 70242
        self.initial_miners = 50
        self.miners_kick_in_at_block=50
        self.miners_drop_at = 0
        self.miners_drop_for=20
        self.jitter_size = 0

    def seeder(self, hasher):
        random.seed(hasher)

    def randomize_params(self):
        self.generate_blocks = random.randint(80,500)
        self.new_miner_every_xth_second = random.randint(5,50)
        self.how_many_miners_come_or_go = random.randint(0,1000000)
        self.initial_miners = random.randint(0,1000000)
        self.miners_kick_in_at_block = random.randint(0,40)
        self.jitter_size = random.randint(1,7)
        self.miners_drop_at = random.randint(self.generate_blocks/2,self.generate_blocks)
        pass

    def create_block(self,timestamp, num_pow):
        return {'time_stamp' : timestamp, 'num_pow' : num_pow, 'first_work_factor':0}

    def create_work(self,idx, factor, target):
        return {'id': idx, 'base_executions_per_second' : factor, 'target' : target}

    def addSecs(self,tm, secs):
        fulldate = tm + datetime.timedelta(seconds=secs)
        return fulldate

    def randomDuration(self):
        if self.do_not_randomize_block_times_but_do_always_60_sec:
            return 60
        else:
            return int(random.uniform(25, 120))

    def currently_active_miners(self,current_height):
        if self.current_height            return 0

        if self.current_height>=self.miners_drop_at and self.current_height<=self.miners_drop_at+self.miners_drop_for:
            return 0
        # get the current active number of miners in relation of blockchain height,
        # but the number of miners increases by 1 every 10 blocks
        increases = int(self.current_height/self.new_miner_every_xth_second) * self.how_many_miners_come_or_go
        return self.initial_miners+increases

    def miner_pows_based_on_target(self,work, height, dur):
        current_target = work["target"]
        factor = (current_target / self.base_target) * 1.0*dur/60.0
        actual_pow_mined = work["base_executions_per_second"]
        # random jitter
        actual_pow_mined = abs((actual_pow_mined - self.jitter_size) + random.uniform(self.jitter_size,2*self.jitter_size)) * self.currently_active_miners(height)
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        actual_pow_mined = actual_pow_mined *factor
        # rate limit to 20 pows per block
        if actual_pow_mined > 20:
            actual_pow_mined = 20
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        return actual_pow_mined
    def kimoto(self,x):
        return  1 + (0.7084 * pow(((x)/(144)), -1.228));
       
    def retarget_work(self,block, x):
        targetI = x["target"]
        pastMass = 0
        account_for_block_max = 10
        seconds_passed = 0
        totalMass = 0
        counter = 0
        current_block = block
        current_block_timestamp = self.blockchain[current_block]["time_stamp"]

        massive_retarget = False
        deviation_too_high = False
        last_two_deviation = 0.0

        while True:
            counter = counter + 1
            curmass = self.blockchain[current_block]["num_pow"][x["id"]]
            pastMass += curmass


            # if the maximum block-tx-limit of 20 was reached, do massive retargeting
            if counter == 1 and pastMass == 20:
                massive_retarget = True
                break

            # Also if deviation of last two block was too high, do some "magic"
            if counter == 1 and curmass > 0:
                last_two_deviation = curmass / 10
                if last_two_deviation > 1.25 or last_two_deviation < -0.75:  #deviation of over 25% is bad
                    print "last two deviation",last_two_deviation,"at block",block
                    deviation_too_high = True
                    break




            for y in self.blockchain[current_block]["num_pow"]:
                totalMass += self.blockchain[current_block]["num_pow"][y]
            seconds_passed = (current_block_timestamp - self.blockchain[current_block-1]["time_stamp"]).seconds
            current_block = current_block - 1
           
            if current_block < 1 or seconds_passed >= 60: # retarget every 120 seconds ~ 1 block on average
                break

        factor = 1
        if massive_retarget == True:
            factor = 0.4 # lower to just 40%
        elif deviation_too_high == True:
            factor = 1/last_two_deviation
        else:
            if seconds_passed < 1:
                seconds_passed = 1

            pows_per_360_seconds = ((pastMass * 360.0) / seconds_passed)
            if pows_per_360_seconds>0 and pows_per_360_seconds<1:
                pows_per_360_seconds = 1

            factor = 1
            if pows_per_360_seconds > 0:
                factor = 10*6.0/pows_per_360_seconds
                if factor<0.9:
                    factor = 0.9
                if factor>1.1:
                    factor=1.1
            elif pows_per_360_seconds == 0 and totalMass == 0:
                factor = 1.05
            else:
                factor = 1

        #print "seconds",seconds_passed,"blocks",counter,"actual pows",pastMass,"per 360s:",pows_per_360_seconds,"wanted:",60,"factor",factor

        targetI = targetI * factor
        if targetI>self.base_target:
            targetI = self.base_target
        if x["id"]==0:
            self.blockchain[block]["first_work_factor"] = factor
        x["target"] = targetI



    def retarget_works(self,block):
        for x in self.work_packages:
            self.retarget_work(block,x)


    def reset_chain(self):
        self.blockchain = []
        self.work_packages = []
        self.current_height = 0
        self.current_time = datetime.datetime.now()

        # Here we create up to three different work objects
        if self.works_to_create>=1:
            self.work_packages.append(self.create_work(0, 20, self.base_target))
        if self.works_to_create>=2:
            self.work_packages.append(self.create_work(1, 60, self.base_target))
        if self.works_to_create>=3:
            self.work_packages.append(self.create_work(2, 35, self.base_target))

    def generate_chain(self):
        while self.current_height < self.generate_blocks:
            if self.current_height%1000==0:
                print "  -> generated block",self.current_height
            dur = self.randomDuration()
            self.current_time = self.addSecs(self.current_time,dur) # random block generation time
            block_pows = {}
            for x in self.work_packages:
                num_pow = self.miner_pows_based_on_target(x, self.current_height, dur) # mine some POW depending on the current difficulty
                block_pows[x["id"]] = num_pow
            self.blockchain.append(self.create_block(self.current_time, block_pows))
            self.retarget_works(self.current_height) # This retargeting method is the "critical part here"
            self.current_height = self.current_height + 1

    def get_total_error(self):
        values = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)


            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        #print values
        #print ideal
        total_error = 0
        for x in range(len(values)):
            soll = ideal[x]
            ist = values[x]
            total_error += abs(soll-ist)
        return total_error/len(values)

    def plot(self,run):
        values = []
        target_factors = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)

            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values

        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        #fig = plt.figure()
        ax0 = plt.subplot(211)
        if self.stretch_number_pows:
            ax0.set_ylabel('POW rate per 60s', color='b')
        else:
            ax0.set_ylabel('POWs per Block', color='b')
        ax0.set_xlabel('Block height')
        ax0.plot(x,y,'-o',x,ideal,'r--')
        values = []
        ideal = []
        target_factors = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            sum_x += x["num_pow"][0]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(10*strech_normalizer)
                else:
                    ideal.append(10)

            #print "sumx",sum_x
            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values
        plt.title('All Works: Total POWs')
        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        ax1 = plt.subplot(212)
        ax1.plot(x,y,'-o',x,ideal,'r--')
        ax1.set_xlabel('Block Height')
        # Make the y-axis label and tick labels match the line color.
        if self.stretch_number_pows:
            ax1.set_ylabel('POW rate per 60s', color='b')
        else:
            ax1.set_ylabel('POWs per Block', color='b')

        for tl in ax1.get_yticklabels():
            tl.set_color('b')



        ax2 = ax1.twinx()
        ax2.set_ylim(0.4, 1.6)
        ax2.bar(x,[x["first_work_factor"] for x in self.blockchain][1:],0.45,color='#deb0b0', alpha=0.2)
        ax2.set_ylabel('Retargeting Factor', color='r')
        for tl in ax2.get_yticklabels():
            tl.set_color('r')
        plt.title('First Work: POWs + Retargeting Factor')

        plt.savefig('render-' + str(run) + '.png')
        plt.close()
legendary
Activity: 1260
Merit: 1168
I'll fix the test setup to estimate the "desired POW rate" more correctly  Wink

EDIT:

Like this maybe:

Code:
def currently_active_miners(self,current_height):
        if current_height            return 0

        if current_height>=self.miners_drop_at and current_height<=self.miners_drop_at+self.miners_drop_for:
            return 0
        # get the current active number of miners in relation of blockchain height,
        # but the number of miners increases by 1 every 10 blocks
        increases = int(current_height/self.new_miner_every_xth_second) * self.how_many_miners_come_or_go
        return self.initial_miners+increases

def get_total_error(self):
        values = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
          
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if self.currently_active_miners(idx) == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)


            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        #print values
        #print ideal
        total_error = 0
        for x in range(len(values)):
            soll = ideal[x]
            ist = values[x]
            total_error += abs(soll-ist)
        return total_error/len(values)
legendary
Activity: 1260
Merit: 1168
Note: If anyone of you disagrees with the testing method, please ask before the contest ends ;-) We can improve the tests of course. But that has to be done before the seeding hash is known!

It would probably be ideal to be able to test more behaviors.  As it is, it seems like an algorithm that wins the contest easily could significantly under-perform in less uniform churn behavior scenarios.

Open to any suggestions ;-)
In the current test setup I tried to account for:

- In the beginning, nobody does anything
- At some early block, a large (or not so large) amount of miners enter the game
- At fixed intervals, new miners join the network
- Mining power is not uniform but jittered, also with a random intensity
- During some interval in the second half of the test run, all miners leave and rejoin later (tried to simulate some multipool behaviour)
- The block time itself is jittered as well, to simulate the variations in the actual block generation time
sr. member
Activity: 434
Merit: 250
Note: If anyone of you disagrees with the testing method, please ask before the contest ends ;-) We can improve the tests of course. But that has to be done before the seeding hash is known!

Here is my first entry, in pseudocode:
Code:
alwas set all targets to constant value 0xFF;

This has a perfect 0.0 error rate in your test harness, so it can't be beaten with any better score.  Both ideal and simulated work counts come out to 0 per block, every block, every time, regardless of randomizer seed.

I WIN!(?!?!)

 Cool
newbie
Activity: 1
Merit: 0
A totally experimental creation of mine. At least it beats the error of the other submissions.

Code:
import datetime
import random
import numpy as np
import matplotlib.pyplot as plt

# sudo apt-get install python-tk
# pip2 install numpy matplotlib


class RetargetTest():
    # experiment with the number of work packages
    def __init__(self):
        self.current_time = datetime.datetime.now()
        self.works_to_create = 3
        self.generate_blocks = 100
        self.current_height = 0
        self.blockchain = []
        self.work_packages = []
        self.base_target = 0x000000ffffffffffffffffffffffffff
        self.stretch_number_pows = True
        self.do_not_randomize_block_times_but_do_always_60_sec = True
        self.new_miner_every_xth_second = 10
        self.how_many_miners_come_or_go = 70242
        self.initial_miners = 50
        self.miners_kick_in_at_block=50
        self.miners_drop_at = 0
        self.miners_drop_for=20
        self.jitter_size = 0

    def seeder(self, hasher):
        random.seed(hasher)

    def randomize_params(self):
        self.generate_blocks = random.randint(80,500)
        self.new_miner_every_xth_second = random.randint(5,50)
        self.how_many_miners_come_or_go = random.randint(0,1000000)
        self.initial_miners = random.randint(0,1000000)
        self.miners_kick_in_at_block = random.randint(0,40)
        self.jitter_size = random.randint(1,7)
        self.miners_drop_at = random.randint(self.generate_blocks/2,self.generate_blocks)
        pass

    def create_block(self,timestamp, num_pow):
        return {'time_stamp' : timestamp, 'num_pow' : num_pow, 'first_work_factor':0}

    def create_work(self,idx, factor, target):
        return {'id': idx, 'base_executions_per_second' : factor, 'target' : target}

    def addSecs(self,tm, secs):
        fulldate = tm + datetime.timedelta(seconds=secs)
        return fulldate

    def randomDuration(self):
        if self.do_not_randomize_block_times_but_do_always_60_sec:
            return 60
        else:
            return int(random.uniform(25, 120))

    def currently_active_miners(self,current_height):
        if self.current_height            return 0

        if self.current_height>=self.miners_drop_at and self.current_height<=self.miners_drop_at+self.miners_drop_for:
            return 0
        # get the current active number of miners in relation of blockchain height,
        # but the number of miners increases by 1 every 10 blocks
        increases = int(self.current_height/self.new_miner_every_xth_second) * self.how_many_miners_come_or_go
        return self.initial_miners+increases

    def miner_pows_based_on_target(self,work, height, dur):
        current_target = work["target"]
        factor = (current_target / self.base_target) * 1.0*dur/60.0
        actual_pow_mined = work["base_executions_per_second"]
        # random jitter
        actual_pow_mined = abs((actual_pow_mined - self.jitter_size) + random.uniform(self.jitter_size,2*self.jitter_size)) * self.currently_active_miners(height)
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        actual_pow_mined = actual_pow_mined *factor
        # rate limit to 20 pows per block
        if actual_pow_mined > 20:
            actual_pow_mined = 20
        if actual_pow_mined < 0:
            actual_pow_mined = 0
        return actual_pow_mined
    def kimoto(self,x):
        return  1 + (0.7084 * pow(((x)/(144)), -1.228))

    def retarget_work(self,block, x):
        targetI = x["target"]
        pastMass = 0
        counter = 0
        current_block = block
        current_block_timestamp = self.blockchain[current_block]["time_stamp"]
        adjustment = 0
        max_block_reading = 44
        min_block_reading = 2

        lowerbound = 0.2
        higherbound = 1.8

        minval = 0x000000ffffffffffffffffffffffffff
        maxval = 0

        ultra_aggressive = False
        modest_aggressive = False
        was_negative = False
        seconds_passed = 0
        while True:
            counter += 1
            currpows = self.blockchain[current_block]["num_pow"][x["id"]]
            pastMass += currpows
            if currpows                minval=currpows
            if currpows>maxval:
                maxval=currpows
            old_seconds_passed = seconds_passed
            seconds_passed = (current_block_timestamp - self.blockchain[current_block-1]["time_stamp"]).seconds
            if current_block_timestamp < self.blockchain[current_block-1]["time_stamp"]:
                current_block_timestamp = self.blockchain[current_block-1]["time_stamp"]
            current_block -= 1
            if seconds_passed < 1:
                seconds_passed = 1

            seconds_target_passed = pastMass * 6 # 6 seconds per POW
            adj = seconds_target_passed / seconds_passed
           
            # break if  fluctuations are too heavy
            if counter*20 == pastMass and counter >= min_block_reading:
                ultra_aggressive = True
                break

            if counter==1:
                if adj<1:
                    was_negative = True
            else:
                if adj<1 and was_negative == False:
                    seconds_passed = old_seconds_passed
                    pastMass -= currpows
                    break
                if adj>=1 and was_negative == True:
                    seconds_passed = old_seconds_passed
                    pastMass -= currpows
                    break

            if minval > 0 and maxval/minval>1.5:
                modest_aggressive = True

            if minval > 0 and maxval/minval>1.5 and counter >= min_block_reading:
                break

            if current_block < 1 or counter == max_block_reading:
                break
        print "used",counter
        lowerbound = min(0.75,lowerbound+counter*0.25)
        higherbound = max(1.25,higherbound-counter*0.25)
       
        if pastMass>=1 and ultra_aggressive==False:
            seconds_target_passed = pastMass * 6 # 6 seconds per POW
            adj = seconds_target_passed / seconds_passed
            if adj                adj=lowerbound
            if adj>higherbound:
                adj=higherbound

            if modest_aggressive and adj>=1:
                adj *= 1.2
            if modest_aggressive and adj<1:
                adj /= 1.2

            targetI /= adj
        elif pastMass>=1 and ultra_aggressive==True:
            targetI = targetI/(3*counter)
        else:
            targetI *= 2


        if targetI>self.base_target:
            targetI = self.base_target
        if targetI<1000:
            targetI=1000
        if x["id"] == 0:
            self.blockchain[block]["first_work_factor"] = adjustment
        x["target"] = targetI
        #print "Retarget using",counter,"blocks","fullcnt",fullCnt,"emptyCnt",emptyCnt


    def retarget_works(self,block):
        for x in self.work_packages:
            self.retarget_work(block,x)


    def reset_chain(self):
        self.blockchain = []
        self.work_packages = []
        self.current_height = 0
        self.current_time = datetime.datetime.now()

        # Here we create up to three different work objects
        if self.works_to_create>=1:
            self.work_packages.append(self.create_work(0, 20, self.base_target))
        if self.works_to_create>=2:
            self.work_packages.append(self.create_work(1, 60, self.base_target))
        if self.works_to_create>=3:
            self.work_packages.append(self.create_work(2, 35, self.base_target))

    def generate_chain(self):
        while self.current_height < self.generate_blocks:
            if self.current_height%1000==0:
                print "  -> generated block",self.current_height
            dur = self.randomDuration()
            self.current_time = self.addSecs(self.current_time,dur) # random block generation time
            block_pows = {}
            for x in self.work_packages:
                num_pow = self.miner_pows_based_on_target(x, self.current_height, dur) # mine some POW depending on the current difficulty
                block_pows[x["id"]] = num_pow
            self.blockchain.append(self.create_block(self.current_time, block_pows))
            self.retarget_works(self.current_height) # This retargeting method is the "critical part here"
            self.current_height = self.current_height + 1

    def get_total_error(self):
        values = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)


            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        #print values
        #print ideal
        total_error = 0
        for x in range(len(values)):
            soll = ideal[x]
            ist = values[x]
            total_error += abs(soll-ist)
        return total_error/len(values)

    def plot(self,run):
        values = []
        target_factors = []
        ideal = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            for y in x["num_pow"]:
                sum_x += x["num_pow"][y]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(self.works_to_create*10*strech_normalizer)
                else:
                    ideal.append(self.works_to_create*10)

            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values

        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        #fig = plt.figure()
        ax0 = plt.subplot(211)
        if self.stretch_number_pows:
            ax0.set_ylabel('POW rate per 60s', color='b')
        else:
            ax0.set_ylabel('POWs per Block', color='b')
        ax0.set_xlabel('Block height')
        ax0.plot(x,y,'-o',x,ideal,'r--')
        values = []
        ideal = []
        target_factors = []
        for idx in range(len(self.blockchain)):
            if idx == 0:
                continue
            x = self.blockchain[idx]
            x_minus_one = self.blockchain[idx-1]
            time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
            strech_normalizer = time_passed / 60.0
           
            sum_x = 0
            sum_x += x["num_pow"][0]

            if sum_x == 0:
                ideal.append(0)
            else:
                if self.stretch_number_pows == False:
                    ideal.append(10*strech_normalizer)
                else:
                    ideal.append(10)

            #print "sumx",sum_x
            if self.stretch_number_pows == False:
                values.append(sum_x)
            else:
                values.append(sum_x/strech_normalizer)
        x = range(self.generate_blocks)[1:]
        y = values
        plt.title('All Works: Total POWs')
        #print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)

        ax1 = plt.subplot(212)
        ax1.plot(x,y,'-o',x,ideal,'r--')
        ax1.set_xlabel('Block Height')
        # Make the y-axis label and tick labels match the line color.
        if self.stretch_number_pows:
            ax1.set_ylabel('POW rate per 60s', color='b')
        else:
            ax1.set_ylabel('POWs per Block', color='b')

        for tl in ax1.get_yticklabels():
            tl.set_color('b')



        ax2 = ax1.twinx()
        ax2.set_ylim(0.4, 1.6)
        ax2.bar(x,[x["first_work_factor"] for x in self.blockchain][1:],0.45,color='#deb0b0', alpha=0.2)
        ax2.set_ylabel('Retargeting Factor', color='r')
        for tl in ax2.get_yticklabels():
            tl.set_color('r')
        plt.title('First Work: POWs + Retargeting Factor')

        plt.savefig('render-' + str(run) + '.png')
        plt.close()
sr. member
Activity: 434
Merit: 250
Note: If anyone of you disagrees with the testing method, please ask before the contest ends ;-) We can improve the tests of course. But that has to be done before the seeding hash is known!

It would probably be ideal to be able to test more behaviors.  As it is, it seems like an algorithm that wins the contest easily could significantly under-perform in less uniform churn behavior scenarios.
sr. member
Activity: 434
Merit: 250
Okay I see, but like I said, this is due to the fact that we can't be aware there is that much miners on the network. All we see is 20 transactions per block. Because of this 20 transactions limit, we can't know how much transaction would have been really mined, so we can't know the hashing power of the network.

We might need to think about this a bit further in the broader context of XEL.  (It does seem like volatility could end up being introduced (to any targeting algorithm) from the work limiting.)
legendary
Activity: 1260
Merit: 1168
Hint ... for the late ones.

what about DGW?
https://github.com/dashpay/dash/blob/master/src/pow.cpp#L82

Or the ppcoin adjustment?

Code:
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
    const PowAlgo algo = pblock->GetAlgo();
    const arith_uint256 bnProofOfWorkLimit = UintToArith256(params.powLimit[algo]);
    const unsigned nProofOfWorkLimit = bnProofOfWorkLimit.GetCompact();

    if (!pindexLast)
        return nProofOfWorkLimit;
    if (params.fPowNoRetargeting)
        return pindexLast->nBits;

    const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, algo);
    if (!pindexPrev || !pindexPrev->pprev)
        return nProofOfWorkLimit; // first block
    const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, algo);
    if (!pindexPrevPrev || !pindexPrevPrev->pprev)
        return nProofOfWorkLimit; // second block

    const int64_t nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime();

    // ppcoin: target change every block
    // ppcoin: retarget with exponential moving toward target spacing
    arith_uint256 bnNew;
    bnNew.SetCompact(pindexPrev->nBits);

    /* The old computation here was:
     
          bnNew *= (nInterval - 1) * nTargetSpacing + 2 * nActualSpacing;
          bnNew /= (nInterval + 1) * nTargetSpacing;

       This, however, may exceed 256 bits for a low difficulty in the
       intermediate step.  We can rewrite it:

          A = (nInterval + 1) nTargetSpacing
          bnNew *= A + 2 (nActualSpacing - nTargetSpacing)
          bnNew /= A

       Or also:

          A = (nInterval + 1) nTargetSpacing
          B = (nActualSpacing - nTargetSpacing)
          bnNew = (bnNew A + bnNew 2 B) / A = bnNew + (2 bnNew B) / A

      To compute (2 bnNew B) / A without overflowing, let

          bnNew = P * A + R.

      Then

          (2 bnNew B) / A = 2 P B + (2 R B) / A.

      Assuming that A is not too large (which it definitely isn't in comparison
      to 256 bits), also (2 R B) does not overflow before the divide.

    */

    const int64_t nInterval = params.DifficultyAdjustmentInterval();

    const int64_t a = (nInterval + 1) * params.nPowTargetSpacing;
    const int64_t b = nActualSpacing - params.nPowTargetSpacing;
    const arith_uint256 p = bnNew / a;
    const arith_uint256 r = bnNew - p * a;

    /* Make sure to get the division right for negative b!  Division is
       not "preserved" under two's complement.  */
    if (b >= 0)
        bnNew += 2 * p * b + (2 * r * b) / a;
    else
        bnNew -= 2 * p * (-b) + (2 * r * (-b)) / a;

    if (bnNew > bnProofOfWorkLimit)
        bnNew = bnProofOfWorkLimit;

    return bnNew.GetCompact();
}

try out and maybe win  Wink
legendary
Activity: 1260
Merit: 1168
So essentially, with the dummy seed right now, loracle's algorithm reaches an error of 6 per block, which is somewhat bad i think! The error is counted in the global case. So on average (of the targetted 30 POW per block, were 6 off)

Code:
Executing run 0
  -> generated block 0
Run 0 - total error = 2.51886191335
Executing run 1
  -> generated block 0
Run 1 - total error = 2.62137314327
Executing run 2
  -> generated block 0
Run 2 - total error = 4.95028524559
Executing run 3
  -> generated block 0
Run 3 - total error = 2.73114641499
Executing run 4
  -> generated block 0
Run 4 - total error = 6.15524345231
Executing run 5
  -> generated block 0
Run 5 - total error = 2.40522822993
Executing run 6
  -> generated block 0
Run 6 - total error = 2.28732108474
Executing run 7
  -> generated block 0
Run 7 - total error = 2.38065310135
Executing run 8
  -> generated block 0
Run 8 - total error = 2.56361782843
Largest error: 6.15524345231
anonymous@bunghole ~/Development/ret





Note: If anyone of you disagrees with the testing method, please ask before the contest ends ;-) We can improve the tests of course. But that has to be done before the seeding hash is known!
copper member
Activity: 2324
Merit: 1348
member
Activity: 151
Merit: 10
This may be Very Difficult,
satoshi Nakamoto would not be able  Shocked
legendary
Activity: 1260
Merit: 1168
Limx, if you want to "enter" you need to provide a variant of this PoC code.

Here is loracle's last version with some "security patches" of mine which prevent "Time Warp" (and a few other) attacks:

Code:
import datetime
import random
import numpy as np
import matplotlib.pyplot as plt

# sudo apt-get install python-tk
# pip2 install numpy matplotlib

def create_block(timestamp, num_pow):
    return {'time_stamp' : timestamp, 'num_pow' : num_pow, 'first_work_factor':0}

def create_work(idx, factor, target):
    return {'id': idx, 'base_executions_per_second' : factor, 'target' : target}

def addSecs(tm, secs):
    fulldate = tm + datetime.timedelta(seconds=secs)
    return fulldate

def randomDuration():
    if do_not_randomize_block_times_but_do_always_60_sec:
        return 60
    else:
        return int(random.uniform(25, 120))

current_time = datetime.datetime.now()

# experiment with the number of work packages
works_to_create = 3

generate_blocks = 100
current_height = 0
blockchain = []
work_packages = []
base_target = 0x000000ffffffffffffffffffffffffff
poisson_distribution = np.random.poisson(5, generate_blocks)
stretch_number_pows = True
do_not_randomize_block_times_but_do_always_60_sec = True
new_miner_every_xth_second = 10
how_many_miners_come_or_go = 70242
initial_miners = 50
miners_kick_in_at_block=50

def currently_active_miners(current_height):
    if current_height        return 0
    # get the current active number of miners in relation of blockchain height,
    # but the number of miners increases by 1 every 10 blocks
    increases = int(current_height/new_miner_every_xth_second) * how_many_miners_come_or_go
    return initial_miners+increases

def miner_pows_based_on_target(work, height, dur):
    current_target = work["target"]
    factor = (current_target / base_target) * 1.0*dur/60.0
    actual_pow_mined = work["base_executions_per_second"]
    # random jitter
    actual_pow_mined = abs((actual_pow_mined - 1) + random.uniform(1,2)) * currently_active_miners(height)
    actual_pow_mined = actual_pow_mined *factor
    # rate limit to 20 pows per block
    if actual_pow_mined > 20:
        actual_pow_mined = 20
    if actual_pow_mined < 0:
        actual_pow_mined = 0
    return actual_pow_mined
def kimoto(x):
    return  1 + (0.7084 * pow(((x)/(144)), -1.228));
def retarget_work(block, x):
    targetI = x["target"]
    pastMass = 0
    counter = 0
    current_block = block
    current_block_timestamp = blockchain[current_block]["time_stamp"]
    adjustment = 0
    isFull = True
    fullCnt = 0
    isEmpty = True
    max_block_reading = 144
    emptyCnt = 0
    while isFull or isEmpty:
        if isFull and blockchain[current_block]["num_pow"][x["id"]] == 20:
            fullCnt += 1
        else:
            isFull = False
        if isEmpty and blockchain[current_block]["num_pow"][x["id"]] == 0:
            emptyCnt += 1
        else:
            isEmpty = False
        current_block -= 1
        if current_block < 1:
            break
    current_block = block
    while True:
        counter += 1
        pastMass += blockchain[current_block]["num_pow"][x["id"]]
        if current_block_timestamp < blockchain[current_block-1]["time_stamp"]:
            current_block_timestamp = blockchain[current_block-1]["time_stamp"]
        seconds_passed = (current_block_timestamp - blockchain[current_block-1]["time_stamp"]).seconds
        current_block -= 1
        if seconds_passed < 1:
            seconds_passed = 1
        trs_per_second = float(pastMass) / float(seconds_passed)
        target_per_second = 10.0 / 60.0
        if trs_per_second > 0:
            adjustment = target_per_second / trs_per_second
            kim = kimoto(pastMass * 30)
            if adjustment > kim or adjustment < (1.0/kim):
                break
        else:
            adjustment = 1
        if current_block < 1 or counter == max_block_reading:
            break

    if fullCnt > 1:
        adjustment = adjustment / (1 << fullCnt)
    if emptyCnt > 1:
        adjustment = adjustment * (1 << emptyCnt)
    targetI = targetI * adjustment
    if targetI>base_target:
            targetI = base_target
    if x["id"] == 0:
            blockchain[block]["first_work_factor"] = adjustment
    x["target"] = targetI
    print "Retarget using",counter,"blocks","fullcnt",fullCnt,"emptyCnt",emptyCnt


def retarget_works(block):
    for x in work_packages:
        retarget_work(block,x)

# Here we create up to three different work objects
if works_to_create>=1:
    work_packages.append(create_work(0, 20, base_target))
if works_to_create>=2:
    work_packages.append(create_work(1, 60, base_target))
if works_to_create>=3:
    work_packages.append(create_work(2, 35, base_target))

while current_height < generate_blocks:
    dur = randomDuration()
    current_time = addSecs(current_time,dur) # random block generation time
    block_pows = {}
    for x in work_packages:
        num_pow = miner_pows_based_on_target(x, current_height, dur) # mine some POW depending on the current difficulty
        block_pows[x["id"]] = num_pow
    blockchain.append(create_block(current_time, block_pows))
    retarget_works(current_height) # This retargeting method is the "critical part here"
    current_height = current_height + 1


values = []
target_factors = []
ideal = []
for idx in range(len(blockchain)):
    if idx == 0:
        continue
    x = blockchain[idx]
    x_minus_one = blockchain[idx-1]
    time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
    strech_normalizer = time_passed / 60.0
    if stretch_number_pows == False:
        ideal.append(works_to_create*10*strech_normalizer)
    else:
        ideal.append(works_to_create*10)
    sum_x = 0
    for y in x["num_pow"]:
        sum_x += x["num_pow"][y]
    if stretch_number_pows == False:
        values.append(sum_x)
    else:
        values.append(sum_x/strech_normalizer)
x = range(generate_blocks)[1:]
y = values

#fig = plt.figure()
ax0 = plt.subplot(211)
if stretch_number_pows:
    ax0.set_ylabel('POW rate per 60s', color='b')
else:
    ax0.set_ylabel('POWs per Block', color='b')
ax0.set_xlabel('Block height')
ax0.plot(x,y,'-o',x,ideal,'r--')
values = []
ideal = []
target_factors = []
for idx in range(len(blockchain)):
    if idx == 0:
        continue
    x = blockchain[idx]
    x_minus_one = blockchain[idx-1]
    time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
    strech_normalizer = time_passed / 60.0
    if stretch_number_pows == False:
        ideal.append(10*strech_normalizer)
    else:
        ideal.append(10)
    sum_x = 0
    sum_x += x["num_pow"][0]
    #print "sumx",sum_x
    if stretch_number_pows == False:
        values.append(sum_x)
    else:
        values.append(sum_x/strech_normalizer)
x = range(generate_blocks)[1:]
y = values
plt.title('All Works: Total POWs')

ax1 = plt.subplot(212)
ax1.plot(x,y,'-o',x,ideal,'r--')
ax1.set_xlabel('Block Height')
# Make the y-axis label and tick labels match the line color.
if stretch_number_pows:
    ax1.set_ylabel('POW rate per 60s', color='b')
else:
    ax1.set_ylabel('POWs per Block', color='b')

for tl in ax1.get_yticklabels():
    tl.set_color('b')



ax2 = ax1.twinx()
ax2.set_ylim(0.4, 1.6)
ax2.bar(x,[x["first_work_factor"] for x in blockchain][1:],0.45,color='#deb0b0', alpha=0.2)
ax2.set_ylabel('Retargeting Factor', color='r')
for tl in ax2.get_yticklabels():
    tl.set_color('r')
plt.title('First Work: POWs + Retargeting Factor')

plt.show()
copper member
Activity: 2324
Merit: 1348
* Kimoto's Gravity Well
   - Auroracoin implementation (from MegaCoin)
   - Dash implementation (above plus handling of negative uint256 and parameter change)
   - Vulnerable to Timewarp Attack (has been carried out on an altcoin).
       timewarp attacks attempt to decrease the difficulty to then mine many coins fast, or with a 51% attack mine a new chain from the genesis block.

* Nite's Gravity Well
  - Implements fix for KGW Timewarp Attack.
  - I can't find any particular reference to the other changes notsofast refers to, and the AuroraCoin source doesn't even appear to use it (they changed     to a different calculation for a multi-PoW-algorithm setup AFAICT).

* DigiShield
   - DigiByte implementation of v3 (there are four versions, see above and below that function).
   - Designed to overcome the issues of the Kimoto Gravity Well algorithm in recovering from large multipool engagements.
   - Asymmetric (allows difficulty to decrease faster than it can increase) .
   - Possibly makes it vulnerable to timewarp attacks, but no proof yet.

* Dark Gravity Wave
   - Dash implementation
 - Combines multiple exponential and simple moving averages to smooth difficulty readjustments and mitigate against exploits in the Kimoto Gravity Well.

I have create the Dual KGW 3 (DK3).



This works already in Europecoin V3/ Bitsend Block 239K and the termdeposit is excatly over months. The function Diffbreak protect the chain. (Reduce the diff after 6h) etc....
I am not sure whether it with elastic works.

member
Activity: 68
Merit: 10
Well, let we have N>20 transactions in the last block and 0
Pages:
Jump to: