Umgang mit Parallel Chains

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: Get-Chain-For-Block hosted at ImgBB — ImgBB (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 Chain 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 … Create-New-Alt-Chain hosted at ImgBB — ImgBB
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:

Hier der Code der Klasse Blockchain in welchem sich die besagten Methoden befinden (zu finden sind diese ganz unten unter dem Kommentar „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);
	}


}

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.

1 „Gefällt mir“

Ahhh das macht Sinn vielen Dank! Also nur nochmal zum Verständnis wenn ein neuer Block bei mir ankommt dann kann davon ausgegangen werden dass sich der Vorgängerblock von diesem zwangsweise in irgendeiner Chain befinden muss und diese liefert mir getChainForBlock() dann zurück oder?

Nein, auf Protokollebene stimmt das so nicht. Du musst da unbedingt zwischen deiner Implementierung und dem Protokoll unterscheiden.

In deinem Code geht die Methode getChainForBlock davon aus, dass der Block bereits im Cache abgelegt wurde. Und wenn sie im Cache liegt, dann ist der Block auch Teil irgendeiner Chain.

Wir wär’s mal mit debuggen?

Achsooo okay
Hmm dachte eigentlich ich häts schon nach deiner ersten Erklärung kapiert aber da lag ich wohl falsch :sweat_smile:
Und ja debuggen muss ich mal versuchen