All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tbk.spring.testcontainer.electrumx.config.ElectrumxContainerAutoConfiguration Maven / Gradle / Ivy

package org.tbk.spring.testcontainer.electrumx.config;

import com.github.dockerjava.api.command.CreateContainerCmd;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.tbk.spring.testcontainer.bitcoind.BitcoindContainer;
import org.tbk.spring.testcontainer.bitcoind.config.BitcoindContainerAutoConfiguration;
import org.tbk.spring.testcontainer.core.CustomHostPortWaitStrategy;
import org.tbk.spring.testcontainer.core.MoreTestcontainers;
import org.tbk.spring.testcontainer.electrumx.ElectrumxContainer;
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.utility.DockerImageName;

import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import static java.util.Objects.requireNonNull;
import static org.tbk.spring.testcontainer.core.MoreTestcontainers.buildInternalContainerUrl;
import static org.tbk.spring.testcontainer.core.MoreTestcontainers.buildInternalHostUrl;

@Slf4j
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ElectrumxContainerProperties.class)
@ConditionalOnProperty(value = "org.tbk.spring.testcontainer.electrumx.enabled", havingValue = "true")
@AutoConfigureAfter(BitcoindContainerAutoConfiguration.class)
public class ElectrumxContainerAutoConfiguration {

    // currently only the image from "lukechilds" is supported
    private static final String DOCKER_IMAGE_NAME = "lukechilds/electrumx:v1.16.0";

    private static final DockerImageName dockerImageName = DockerImageName.parse(DOCKER_IMAGE_NAME);

    private static final int hardcodedRpcPort = 8000;

    private static final List hardcodedStandardPorts = ImmutableList.builder()
            .add(hardcodedRpcPort)
            .add(50001)
            .add(50002)
            .add(50004)
            .build();

    private final ElectrumxContainerProperties properties;

    public ElectrumxContainerAutoConfiguration(ElectrumxContainerProperties properties) {
        this.properties = requireNonNull(properties);
    }

    @Bean(name = "electrumxContainer", destroyMethod = "stop")
    @ConditionalOnMissingBean(BitcoindContainer.class)
    public ElectrumxContainer electrumxContainer(@Qualifier("electrumxContainerWaitStrategy") WaitStrategy waitStrategy) {

        boolean isLocalhost = "localhost".equals(this.properties.getRpchost());
        boolean isLoopback = "127.0.0.1".equals(this.properties.getRpchost());
        boolean isWildcard = "0.0.0.0".equals(this.properties.getRpchost());

        boolean connectsToHost = isLocalhost || isLoopback || isWildcard;
        if (connectsToHost) {
            Testcontainers.exposeHostPorts(this.properties.getRpcport());
        }

        String bitcoindDaemonUrl = connectsToHost ?
                buildLocalDaemonUrl() :
                buildDaemonUrl();

        return createStartedElectrumxContainer(bitcoindDaemonUrl, waitStrategy);
    }

    @Bean(name = "electrumxContainer", destroyMethod = "stop")
    @ConditionalOnBean(BitcoindContainer.class)
    public ElectrumxContainer electrumxContainerWithBitcoindTestcontainer(@Qualifier("electrumxContainerWaitStrategy") WaitStrategy waitStrategy,
                                                                             BitcoindContainer bitcoindContainer) {
        String bitcoindDaemonUrl = buildDaemonUrl(bitcoindContainer);

        return createStartedElectrumxContainer(bitcoindDaemonUrl, waitStrategy);
    }

    @Bean("electrumxContainerWaitStrategy")
    @ConditionalOnMissingBean(name = "electrumxContainerWaitStrategy")
    public WaitStrategy electrumxContainerWaitStrategy() {
        // only listen for rpc port as other ports might not be opened because of the initial sync!
        // from the docs (https://electrumx-spesmilo.readthedocs.io/en/latest/HOWTO.html#sync-progress):
        // > ElectrumX will not serve normal client connections until it has fully synchronized and caught up with
        // > your daemon. However LocalRPC connections are served at all times.
        return CustomHostPortWaitStrategy.builder()
                .addPort(hardcodedRpcPort)
                .build();
    }

    private ElectrumxContainer createStartedElectrumxContainer(String bitcoindDaemonUrl, WaitStrategy waitStrategy) {

        ElectrumxContainer electrumxContainer = createElectrumxContainer(bitcoindDaemonUrl, waitStrategy);

        electrumxContainer.start();

        // expose all mapped ports of the host so other containers can communication with electrumx
        MoreTestcontainers.exposeAllPortsToOtherContainers(electrumxContainer);

        return electrumxContainer;
    }

    private ElectrumxContainer createElectrumxContainer(String bitcoindDaemonUrl, WaitStrategy waitStrategy) {
        Map env = buildEnvMap(bitcoindDaemonUrl);

        ElectrumxContainer electrumxContainer = new ElectrumxContainer<>(dockerImageName)
                .withCreateContainerCmdModifier(cmdModifier())
                .withExposedPorts(hardcodedStandardPorts.toArray(new Integer[]{}))
                .withEnv(env)
                .waitingFor(waitStrategy);

        return electrumxContainer;
    }

    private Consumer cmdModifier() {
        return MoreTestcontainers.cmdModifiers().withName(dockerContainerName());
    }

    private String dockerContainerName() {
        return String.format("%s-%s", dockerImageName.getUnversionedPart(),
                Integer.toHexString(System.identityHashCode(this)))
                .replace("/", "-");
    }

    private Map buildEnvMap(String bitcoindDaemonUrl) {
        Map environmentWithDefaults = this.properties.getEnvironmentWithDefaults();

        if (environmentWithDefaults.containsKey("DAEMON_URL")) {
            throw new IllegalStateException("'DAEMON_URL' is not allowed");
        }

        return ImmutableMap.builder()
                .putAll(environmentWithDefaults)
                .put("DAEMON_URL", bitcoindDaemonUrl)
                .build();
    }

    private String buildDaemonUrl() {
        return String.format("http://%s:%s@%s:%d",
                this.properties.getRpcuser(),
                this.properties.getRpcpass(),
                this.properties.getRpchost(),
                this.properties.getRpcport()
        );
    }

    private String buildDaemonUrl(BitcoindContainer bitcoindContainer) {
        return buildInternalContainerUrl(
                bitcoindContainer,
                "http",
                this.properties.getRpcuser(),
                this.properties.getRpcpass(),
                this.properties.getRpcport()
        );
    }

    private String buildLocalDaemonUrl() {
        return buildInternalHostUrl(
                "http",
                this.properties.getRpcuser(),
                this.properties.getRpcpass(),
                this.properties.getRpcport()
        );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy