Zum Inhalt springen

Umgang mit Parallel Chains


Empfohlene Beiträge

Hallo zusammen,

ich arbeite gegenwärtig ein Buch zum Thema Blockchain Entwicklung durch weil ich mich mit dem Thema genauer befassen möchte. Allerdings bin ich auf ein kleines Verständnisproblem gestoßen...

Das Ganze ist in Java geschrieben und im aktuellen Kapitel geht es darum, wie die Blockchain mit parallelen Chains umgehen soll, also was passieren soll, wenn beispielsweise zwei Miner gleichzeitig einen Block finden.
Allerdings kann ich eine Methode logisch nicht ganz nachvollziehen, vielleicht könnt ihr mir helfen.
Ich kann die erste Methode noch verstehen: https://ibb.co/fYdZSnr (Link zum Code-Bild von ImgBB)
Hier geht es darum, für einen neu ankommenden Block die richtige Chain zu finden, also zu prüfen, ob der Block tatsächlich zur aktuell längsten Chain (im Code „chain“) gehört. Dazu wird überprüft, ob sich in diesem der Index des Blocks befindet. Wenn dies der Fall ist, wird diese Kette von der Methode zurückgegeben. Ist dies nicht der Fall, werden die alternativen Chains (altChains im Code) geprüft und bei einem Treffer zurückgegeben.
Findet sich nirgendwo ein Match, heißt es im Buch „in diesem Fall ist davon auszugehen, dass es noch eine weitere Kette im Netz gibt, die man noch selbst erstellen muss“. Die Methode gibt also "null" zurück. (Fühlt euch frei mich zu korrigieren, wenn ich falsch liege).

Bei der zweiten Methode geht es nun darum, diese Chain zu erstellen, wenn keine Übereinstimmung gefunden wurde. Und ich kann das folgende Verfahren nicht ganz verstehen ... https://ibb.co/hM2PVsx
Zunächst ruft die Methode die eben erwähnte Methode getChainForBlock auf und ermittelt die Chain des neu angekommenen Blocks (bzw. übergeben wird der Hash des Vorgängerblocks des neu geminten Blocks). Dann wird anhand dieser Vorgänger-ID des neuen Blocks der Index ermittelt, an dem sich der Vorgängerblock befindet. Anschließend werden sowohl der Vorgängerblock als auch alle Vorgänger in eine neue Liste kopiert. Der neue Block muss dann dieser Liste hinzugefügt werden . Dann kann eine neue Chain erstellt werden, die zu den alternativen Chains hinzugefügt wird..."
Was ich hier also nicht verstehe ist folgendes: Die Methode "getChainForBlock" gibt "null" zurück, wenn die Kette bei mir noch nicht existiert, oder? Wie ist es dann möglich über die Liste vom Rückgabetyp "null" zu iterieren, der die vorherigen Blöcke kopiert und so weiter?

Hier findet ihr einen Link zu den entsprechenden Seiten im Buch die das ganze erklären sollen:

https://ibb.co/YZDRkQL

https://ibb.co/8bzpbVV

https://ibb.co/VDFfPD5

https://ibb.co/fDvvKKb

Und hier wäre meine Klasse Blockchain mit den entsprechenden Methoden (die beiden speziell erwähnten finden sich ganz unten unter der Kommentierung "AltChains"):

public class Blockchain
{
	private static Logger logger = Logger.getLogger( Blockchain.class );

	public final static int MAX_BLOCK_SIZE_IN_BYTES = 1120;

	public final static int NETWORK_ID = 1;

	private BigInteger difficulty;

	private Chain chain;

	private Map<String, Block> blockCache;

	private Map<String, Transaction> transactionCache;
	
	private List<Chain> altChains;
	
	private Block bestBlock;

	public Blockchain( )
	{
		this.chain = new Chain( NETWORK_ID );
		this.blockCache = new ConcurrentHashMap<>( );
		this.transactionCache = new ConcurrentHashMap<>( );
		this.altChains = new CopyOnWriteArrayList<Chain>();
		altChains.add(chain);
		this.bestBlock = chain.getLast();
		this.difficulty = new BigInteger(
			"-578950000000000000000000000000000000000000000000000000000000000" );
	}
	
	public Blockchain( BigInteger difficulty, List<Chain> altChains )
	{
		this.difficulty = difficulty;
		this.altChains = altChains;

		this.blockCache = new ConcurrentHashMap<>( );
		this.transactionCache = new ConcurrentHashMap<>( );

		int max = 0;
		Chain chain = null;

		for ( Chain altChain : altChains )
		{
			if ( max < altChain.size( ) )
			{
				max = altChain.size( );
				chain = altChain;
			}

			for ( Block block : altChain.getChain( ) )
			{
				this.blockCache.put( SHA3Helper.digestToHex( block.getBlockHash( ) ), block );

				for ( Transaction transaction : block.getTransactions( ) )
				{
					this.transactionCache.put( transaction.getTxIdAsString( ), transaction );
				}
			}
		}

		this.chain = chain;
		this.bestBlock = chain.getLast( );
	}

	public synchronized void addBlock( Block block )
	{
		byte[] previousBlockHash = block.getBlockHeader().getPreviousHash();
		if(previousBlockIsBestBlock(previousBlockHash)) {
			logger.debug( "previous block was best block" );
			chain.add(block);
			bestBlock = block;
		}
		
		else {
			logger.debug( "previous block wasnt best block: available Chains: " + altChains.size( ) );
			checkAltChains(previousBlockHash, block);
		}
		
		blockCache.put( SHA3Helper.digestToHex( block.getBlockHash( ) ), block );

		for ( Transaction transaction : block.getTransactions( ) )
		{
			transactionCache.put( transaction.getTxIdAsString( ), transaction );
		}
	}
	
	public List<Chain> getAltChains( )
	{
		return altChains;
	}

	public boolean fulfillsDifficulty( byte[] digest )
	{
		BigInteger temp = new BigInteger( digest );

		return temp.compareTo( difficulty ) <= 0;
	}

	public BigInteger getDifficulty( )
	{
		return difficulty;
	}

	public void setDifficulty( BigInteger difficulty )
	{
		this.difficulty = difficulty;
	}

	public byte[] getPreviousHash( )
	{
		return chain.getLast( ).getBlockHash( );
	}

	public int size( )
	{
		return chain.size( );
	}

	public Transaction getTransactionByHash( String hex )
	{
		return transactionCache.get( hex );
	}

	public Block getBlockByHash( String hex )
	{
		return blockCache.get( hex );
	}

	public Block getBlockByHash( byte[] hash )
	{
		return blockCache.get( SHA3Helper.digestToHex( hash ) );
	}

	public Block getLatestBlock( )
	{
		return chain.getLast( );
	}

	public List<Block> getLatestBlocks( int size, int offset )
	{
		List<Block> blocks = new ArrayList<>( );

		Block block = this.getLatestBlock( );

		for ( int i = 0; i < ( size + offset ); i++ )
		{
			if ( block != null )
			{
				if ( i >= offset )
				{
					blocks.add( block );
				}

				String previousHash = SHA3Helper.digestToHex( block.getBlockHeader( ).getPreviousHash( ) );
				block = this.getBlockByHash( previousHash );
			}
		}

		return blocks;
	}

	public Block getChildOfBlock( Block block )
	{
		return chain.get( chain.getChain( ).indexOf( block ) + 1 );
	}
	
	//AltChains
	private Chain getChainForBlock(Block block) {
		Chain result = null;
		
		int index = chain.getChain().indexOf(block);
		
		if(index == -1) {
			for(Chain altChain: altChains) {
				if(altChain.getChain().indexOf(block) > -1) {
					result = altChain;
				}
			}
		} else {
			result = chain;
		}
		return result;
	}
	
	private void createNewChain(byte[] previousBlockHash, Block block) {
		Chain chain = getChainForBlock(getBlockByHash(previousBlockHash));
		for(int i = chain.getChain().size() - 1; i >= 0; i--) {
			if(Arrays.equals(chain.get(i).getBlockHash(), previousBlockHash)) {
				List<Block> blockList = new CopyOnWriteArrayList<Block>(chain.getChain().subList(0, i + 1));
				blockList.add(block);
				Chain newChain = new Chain(NETWORK_ID, blockList);
				altChains.add(newChain);
				i = -1;
				switchChainsIfNecessary(newChain);
			}
		}		
	}
	
	private void switchChainsIfNecessary(Chain chain) {
		logger.debug("Chains switched");
		if(chain.size() > this.chain.size()) {
			this.chain = chain;
			this.bestBlock = chain.getLast();
		}
	}
	
	private void checkAltChains(byte[] previousHash, Block block) {
		boolean isNotABlockOfAnAltChain = true;
		for(Chain altChain : altChains) {
			if(Arrays.equals(altChain.getLast().getBlockHash(), previousHash)) {
				altChain.add(block);
				switchChainsIfNecessary(altChain);
				isNotABlockOfAnAltChain = false;
				break;
			}
		}
		
		if(isNotABlockOfAnAltChain) {
			createNewChain(previousHash, block);
		}
	}

	private boolean previousBlockIsBestBlock(byte [] BlockHash) {
		return Arrays.equals(DependencyManager.getBlockchain().getPreviousHash(), BlockHash);
	}


}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

On 8/22/2022 at 7:27 PM, Threast said:

Was ich hier also nicht verstehe ist folgendes: Die Methode "getChainForBlock" gibt "null" zurück, wenn die Kette bei mir noch nicht existiert, oder? Wie ist es dann möglich über die Liste vom Rückgabetyp "null" zu iterieren, der die vorherigen Blöcke kopiert und so weiter?

Ohne jetzt tief eingestiegen zu sein - mein letzter Kontakt mit Java ist Jahrzehnte her - vielleicht ist das tatsächlich ein Fehler im Quelltext des Buches, dass der Sonderfall der noch unbekannten Chain zwar im Text erwähnt wird, aber im Quelltext vergessen wurde?
Wie du selber schreibst, kann  getChainForBlock  null zurückgeben, und   chain.getChain().size()  kann nicht funktionieren, wenn  chain  gleich null  ist.

  • Like 1
Link zu diesem Kommentar
Auf anderen Seiten teilen

On 8/22/2022 at 7:27 PM, Threast said:

Was ich hier also nicht verstehe ist folgendes: Die Methode "getChainForBlock" gibt "null" zurück, wenn die Kette bei mir noch nicht existiert, oder? Wie ist es dann möglich über die Liste vom Rückgabetyp "null" zu iterieren, der die vorherigen Blöcke kopiert und so weiter?

Ja, da würde dann in der Tat ne NullPointerExcpetion fliegen. Denke mal das wurde in dem Buch einfach wie schon @Piwi sagt nicht bedacht, oder vergessen oder es ist nen Bug :) 

PS: Finde es witzig das es ein deutsches Buch gibt, welches dann auch noch Blockchain anhand Java Programmierung erklärt.... Wie heisst das Buch ? 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Nabend euch,

ich habe die Frage auch noch in einem anderen Forum gestellt und dazu folgende Antwort erhalten:

"Es gibt immer mindestens eine Chain mit mindestens einem (Genesis) Block. Alle Chains haben damit mindestens einen gemeinsamen Block. "getChainForBlock" muss deshalb für einen ansonsten gültigen Block (Vorgänger Hash etc.) immer ein Chain Objekt zurückgeben. "

Und im Buch wird ja auch beschrieben das die getChainForBlock() Methode auch die Vorvorgänger überprüft und dieser Methode ja auch nicht der Hash des aktuellen Blocks sondern der seines Vorgängers übergeben wird. Dieser muss ja quasi dann irgendwo vorhanden sein, wenn es notfalls auch erst der GenesisBlock ist oder wie seht ihr das?

Link zu diesem Kommentar
Auf anderen Seiten teilen

26 minutes ago, Threast said:

"Es gibt immer mindestens eine Chain mit mindestens einem (Genesis) Block. Alle Chains haben damit mindestens einen gemeinsamen Block. "getChainForBlock" muss deshalb für einen ansonsten gültigen Block (Vorgänger Hash etc.) immer ein Chain Objekt zurückgeben. "

An sich korrekt. Ein Block wird ja nicht aus "der Luft" erzeugt (mit Ausnahme des "vorgegebenen" Genesisblocks), sondern baut immer auf irgendeinem Vorgängerblock auf, sonst ist er von vorneherein ungültig. (*)

Dass sollte aber in sauber programmiertem  Quelltext sowas stehen wie: result = null can never happen. Das fehlt hier, was schade und ist - und zumindest ungeschickt für ein Lehrbuch.

(*: Es mag Sonderfälle geben - wie z.B. dass man zwar den Block empfangen hat, aber aus irgendwelchen Gründen den Vorgängerblock noch nicht. Dann kann man auch keine Kette finden, zu der der neue Block gehört. Sollte man in einem guten Quelltext also trotzdem abfangen.)

Bearbeitet von PeWi
Tippfehler
  • Like 1
Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 21 Minuten schrieb PeWi:

An sich korrekt. Ein Block wird ja nicht aus "der Luft" erzeugt (mit Ausnahme des "vorgegebenen" Genesisblocks), sondern baut immer auf irgendeinem Vorgängerblock auf, sonst ist er von vorneherein ungültig. (*)

Dass sollte aber in sauber programiertem  Quelltext sowas stehen wie: result = null can never happen. Das fehlt hier, was schade und ist - und zumindest ungeschickt für ein Lehrbuch.

(*: Es mag Sonderfälle geben - wie z.B. dass man zwar den Block empfangen hat, aber aus irgendwelchen Gründen den Vorgängerblock noch nicht. Dann kann man auch keine Kette finden, zu der der neue Block gehört. Sollte man in einem guten Quelltext also trotzdem abfangen.)

Ich denke ich verstehe was du meinst das ganze ist wirklich sehr schlecht beschrieben... 

Aber würde die Methode bzw die Methoden dann rein technisch gesehen so funktionieren und nie null zurückgeben? 

Link zu diesem Kommentar
Auf anderen Seiten teilen

9 minutes ago, Threast said:

Aber würde die Methode bzw die Methoden dann rein technisch gesehen so funktionieren und nie null zurückgeben? 

In einer idealen Welt bzw im Probierraum des Buches funktioniert die Methode und gibt nie null zurück, weil in der idealen Welt nie ein Block verloren geht oder später als sein Nachfolger empfangen wird..

In der realen Welt - oder wenn du die Blockchain des Buches mit entfernten Freunden ausprobierst, und das Internet mal wackelt - dann kann durchaus mal ein Block verloren gehen oder später kommen, und dann ergibt die Methode getChainForBlock tatsächlich null, was in der darüber liegenden Methode createNewChain wie von @¯\_(ツ)_/¯ bestätigt zu einer NullPointerException führen und deine Blockchain zum Abbruch bringen würde.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 28 Minuten schrieb PeWi:

In einer idealen Welt bzw im Probierraum des Buches funktioniert die Methode und gibt nie null zurück, weil in der idealen Welt nie ein Block verloren geht oder später als sein Nachfolger empfangen wird..

In der realen Welt - oder wenn du die Blockchain des Buches mit entfernten Freunden ausprobierst, und das Internet mal wackelt - dann kann durchaus mal ein Block verloren gehen oder später kommen, und dann ergibt die Methode getChainForBlock tatsächlich null, was in der darüber liegenden Methode createNewChain wie von @¯\_(ツ)_/¯ bestätigt zu einer NullPointerException führen und deine Blockchain zum Abbruch bringen würde.

Ahh ich verstehe,  vielen Dank für die ausführliche Erklärung👍 Es kommt noch ein Kapitel zum Thema Optimierung am Ende vielleicht wird da diese Problematik ja nochmal angesprochen mal sehen😅

  • Like 1
Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 1 Stunde schrieb ¯\_(ツ)_/¯:

Ja, da würde dann in der Tat ne NullPointerExcpetion fliegen. Denke mal das wurde in dem Buch einfach wie schon @Piwi sagt nicht bedacht, oder vergessen oder es ist nen Bug :) 

PS: Finde es witzig das es ein deutsches Buch gibt, welches dann auch noch Blockchain anhand Java Programmierung erklärt.... Wie heisst das Buch ? 

Das Buch heißt Blockchain für Entwickler🙂

Link zu diesem Kommentar
Auf anderen Seiten teilen

Erstelle ein Benutzerkonto oder melde Dich an, um zu kommentieren

Du musst ein Benutzerkonto haben, um einen Kommentar verfassen zu können

Benutzerkonto erstellen

Neues Benutzerkonto für unsere Community erstellen. Es ist einfach!

Neues Benutzerkonto erstellen

Anmelden

Du hast bereits ein Benutzerkonto? Melde Dich hier an.

Jetzt anmelden
×
×
  • Neu erstellen...

Wichtige Information

Wir haben Cookies auf Deinem Gerät platziert. Das hilft uns diese Webseite zu verbessern. Du kannst die Cookie-Einstellungen anpassen, andernfalls gehen wir davon aus, dass Du damit einverstanden bist, weiterzumachen.