0

I'm building a simple blockchain/cryptocurrency to learn about python and blockchain programming.

I've run into an issue regarding appending transaction objects to the list variable 'transactions' in my Block objects.

For whatever reason, when adding a transaction to a block, it is added to every block on the chain.

I have uploaded my code to a github repo: The project consists of 3 class files: Blockchain.py, Block.py & Transaction.py I also have a testing file 'test1.py' which reproduces the error.

https://github.com/swooperior/blockchain-py

I suspect the issue is in the Block class file:

#Not intended behaviour.  addTransaction seems to add to every block in self.chain

from datetime import datetime
import hashlib
class Block:
    hash = ''
    txIndex = 0
    transactions = []
    timeStamp = ''
    previous_hash = ''
    nonce = 0

    def calculateHash(self):
        self.hash = str(hashlib.sha256(repr([self.transactions,self.previous_hash,self.nonce]).encode('utf-8')).hexdigest())

    def getHash(self):
        return self.hash




    def addTransaction(self,tx):
        #Validate transaction, then pass to transactions list
        tx.id = self.txIndex
        self.transactions.append(tx)
        self.txIndex += 1

    def printDetails(self):
        print('Block Hash: '+self.getHash())
        print('Nonce: '+str(self.nonce))
        print('Created: '+ str(datetime.fromtimestamp(self.timeStamp)))
        print('Prev_hash: '+self.previous_hash)
        print('Transactions ('+str(len(self.transactions))+'):')
        self.printTransactions()

    def printTransactions(self):
        c = 1
        for tx in self.transactions:
            print('Transaction:'+ str(c))
            tx.printDetails()
            c += 1

    def __init__(self,txlist=[],prev_hash=''):
        self.txIndex = 0
        self.previous_hash = prev_hash
        for tx in txlist:
            self.addTransaction(tx)
        self.timeStamp = datetime.timestamp(datetime.now())
        self.nonce = 1
        self.calculateHash()
        #print(self.printDetails())
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • 1
    https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument and/or http://stackoverflow.com/questions/1680528/how-do-i-avoid-having-python-class-data-shared-among-instances. Basically none of your state is actually instance-level. – jonrsharpe Aug 07 '19 at 21:27
  • 1
    Possible duplicate of [How to avoid having class data shared among instances?](https://stackoverflow.com/questions/1680528/how-to-avoid-having-class-data-shared-among-instances) – juanpa.arrivillaga Aug 07 '19 at 21:50

1 Answers1

1

The transactions attribute is a class attribute for all instances of the class. When you instantiate the class, you should create an instance variable instead. You also shouldn’t use a mutable default argument.

class Block:
    ...
    def __init__(self, txlist=None, prev_hash=''):
        self.transactions = []
        txlist = txlist or []
        self.previous_hash = prev_hash
        for tx in txlist:
            self.addTransaction(tx)
        self.timeStamp = datetime.timestamp(datetime.now())
        self.nonce = 1
        self.calculateHash()

Function defaults are only evaluated once so each instance uses the same default argument unless you give it another one. This only happens to mutable objects as re-assigning them doesn’t copy them.

N Chauhan
  • 3,407
  • 2
  • 7
  • 21