Last active
August 1, 2018 15:36
-
-
Save Glamdring/30f20ad2afe9ac5efe340d5456c891d3 to your computer and use it in GitHub Desktop.
Sample EthereumJ transaction sending
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.mypackage.ethereumsync; | |
import java.io.File; | |
import java.math.BigInteger; | |
import java.util.Base64; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.Random; | |
import java.util.concurrent.CountDownLatch; | |
import java.util.concurrent.ExecutionException; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.Future; | |
import javax.annotation.PostConstruct; | |
import javax.annotation.PreDestroy; | |
import org.apache.commons.codec.binary.Hex; | |
import org.ethereum.config.SystemProperties; | |
import org.ethereum.core.Transaction; | |
import org.ethereum.core.TransactionReceipt; | |
import org.ethereum.crypto.ECKey; | |
import org.ethereum.db.ByteArrayWrapper; | |
import org.ethereum.facade.Ethereum; | |
import org.ethereum.facade.EthereumFactory; | |
import org.ethereum.listener.EthereumListenerAdapter; | |
import org.ethereum.util.ByteUtil; | |
import org.ethereum.util.RLP; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Condition; | |
import org.springframework.context.annotation.ConditionContext; | |
import org.springframework.context.annotation.Conditional; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.context.annotation.PropertySource; | |
import org.springframework.core.type.AnnotatedTypeMetadata; | |
import org.springframework.scheduling.annotation.AsyncResult; | |
import org.springframework.stereotype.Service; | |
import com.google.common.annotations.VisibleForTesting; | |
@Service | |
public class EthereumService { | |
private static final Logger logger = LoggerFactory.getLogger(EthereumService.class); | |
private Map<ByteArrayWrapper, TransactionReceipt> txWaiters = | |
Collections.synchronizedMap(new HashMap<ByteArrayWrapper, TransactionReceipt>()); | |
/** | |
* We have two nodes so that we can send ETH from one to the other and vice versa | |
*/ | |
private Ethereum ethereum1; | |
private Ethereum ethereum2; | |
private CountDownLatch readyLatch = new CountDownLatch(2); | |
private ExecutorService starter; | |
private Random random = new Random(); | |
private Future<?> initFuture; | |
@PostConstruct | |
public void init() throws Exception { | |
starter = Executors.newSingleThreadExecutor(); | |
initFuture = starter.submit(this::start); | |
} | |
@PreDestroy | |
public void destroy() { | |
starter.shutdown(); | |
ethereum1.close(); | |
ethereum2.close(); | |
} | |
public void start() { | |
ethereum1 = EthereumFactory.createEthereum(EthereumConfig1.class); | |
ethereum1.addListener(new EthereumListener(this)); | |
ethereum2 = EthereumFactory.createEthereum(EthereumConfig2.class); | |
ethereum2.addListener(new EthereumListener(this)); | |
} | |
public Future<TransactionReceipt> storeHashInEthereum(String hash) { | |
try { | |
// both clients must be synced before we proceed with storing anything | |
if (EthereumConfig1.PROPERTIES.isSyncEnabled() && EthereumConfig2.PROPERTIES.isSyncEnabled()) { | |
readyLatch.await(); | |
} | |
// we have 2 ethereum clients, and we choose randomly the sender and the receiver | |
SystemProperties senderProps; | |
SystemProperties receiverProps; | |
Ethereum sender; | |
if (random.nextBoolean()) { | |
logger.info("Selected client1 as sender"); | |
sender = ethereum1; | |
senderProps = EthereumConfig1.PROPERTIES; | |
receiverProps = EthereumConfig2.PROPERTIES; | |
} else { | |
logger.info("Selected client2 as sender"); | |
sender = ethereum2; | |
senderProps = EthereumConfig2.PROPERTIES; | |
receiverProps = EthereumConfig1.PROPERTIES; | |
} | |
byte[] senderKey = senderProps.getMyKey().getPrivKeyBytes(); | |
byte[] receiverAddress = receiverProps.getMyKey().getAddress(); | |
TransactionReceipt receipt = sendTxAndWait(senderKey, receiverAddress, Base64.getUrlDecoder().decode(hash), sender); | |
return AsyncResult.forValue(receipt); | |
} catch (Exception ex) { | |
logger.error("Failed to store hash in Ethereum", ex); | |
return AsyncResult.forExecutionException(ex); | |
} | |
} | |
private Future<TransactionReceipt> sendTxAndWait(byte[] senderPrivateKey, byte[] receiveAddress, byte[] data, Ethereum senderNode) throws InterruptedException { | |
byte[] fromAddress = ECKey.fromPrivate(senderPrivateKey).getAddress(); | |
ether | |
BigInteger nonce = senderNode.getRepository().getNonce(fromAddress); | |
Integer chainId = senderNode.getChainIdForNextBlock(); | |
Transaction tx = new Transaction( | |
ByteUtil.bigIntegerToBytes(nonce), | |
ByteUtil.longToBytesNoLeadZeroes(senderNode.getGasPrice()), | |
ByteUtil.longToBytesNoLeadZeroes(200000), | |
receiveAddress, | |
ByteUtil.bigIntegerToBytes(BigInteger.valueOf(1)), // 1 gwei | |
data, | |
chainId); | |
tx.sign(ECKey.fromPrivate(senderPrivateKey)); | |
logger.info("Raw transaction: 0x{}", Hex.encodeHexString(tx.getEncoded())); | |
new Transaction(getEncodedSignedRaw(tx)).verify(); | |
senderNode.submitTransaction(tx); | |
logger.info("<=== Sending transaction: " + tx); | |
return waitForTx(tx.getHash(), senderNode); | |
} | |
private TransactionReceipt waitForTx(byte[] txHash, Ethereum senderNode) throws InterruptedException { | |
ByteArrayWrapper txHashW = new ByteArrayWrapper(txHash); | |
txWaiters.put(txHashW, null); | |
long startBlock = senderNode.getBlockchain().getBestBlock().getNumber(); | |
while(true) { | |
TransactionReceipt receipt = txWaiters.get(txHashW); | |
if (receipt != null) { | |
return receipt; | |
} else { | |
long curBlock = senderNode.getBlockchain().getBestBlock().getNumber(); | |
if (curBlock > startBlock + 16) { | |
throw new RuntimeException("The transaction was not included during last 16 blocks: " + txHashW.toString().substring(0,8)); | |
} else { | |
logger.info("Waiting for block with transaction 0x" + txHashW.toString().substring(0,8) + | |
" included (" + (curBlock - startBlock) + " blocks received so far) ..."); | |
} | |
} | |
synchronized (txHashW) { | |
txHashW.wait(20000); | |
} | |
} | |
} | |
public Ethereum getEthereum1() { | |
return ethereum1; | |
} | |
public Ethereum getEthereum2() { | |
return ethereum2; | |
} | |
@VisibleForTesting | |
public void awaitInitialization() throws InterruptedException, ExecutionException { | |
initFuture.get(); | |
} | |
public static class EthereumListener extends EthereumListenerAdapter { | |
private EthereumService service; | |
public EthereumListener(EthereumService ethereumService) { | |
this.service = ethereumService; | |
} | |
@Override | |
public void onSyncDone(SyncState state) { | |
logger.info("Sync done: state={}", state); | |
service.readyLatch.countDown(); | |
} | |
} | |
/** | |
* Configuring two clients requires separate configuration classes with the same functionality | |
* | |
* @author bozho | |
* | |
*/ | |
@Configuration | |
@Conditional(value=ConfigurationRegistrationCondition.class) | |
@PropertySource("classpath:/ethereumsync.properties") | |
public static class EthereumConfig1 { | |
public static SystemProperties PROPERTIES; | |
@Value("${ethereumj.config.path}") | |
private String configPath; | |
@Bean | |
public SystemProperties systemProperties() { | |
if (PROPERTIES == null) { | |
PROPERTIES = new SystemProperties(new File(configPath + "/user1.conf")); | |
logger.info("Address for client1: 0x{}", Hex.encodeHexString(PROPERTIES.getMyKey().getAddress())); | |
} | |
return PROPERTIES; | |
} | |
} | |
@Configuration | |
@Conditional(value=ConfigurationRegistrationCondition.class) | |
@PropertySource("classpath:/ethereumsync.properties") | |
public static class EthereumConfig2 { | |
public static SystemProperties PROPERTIES; | |
@Value("${ethereumj.config.path}") | |
private String configPath; | |
@Bean | |
public SystemProperties systemProperties() { | |
if (PROPERTIES == null) { | |
PROPERTIES = new SystemProperties(new File(configPath + "/user2.conf")); | |
logger.info("Address for client2: 0x{}", Hex.encodeHexString(PROPERTIES.getMyKey().getAddress())); | |
} | |
return PROPERTIES; | |
} | |
} | |
// needed in order to not register EthereumConfigs with the main context, just with the nested ethereumj ones | |
public static class ConfigurationRegistrationCondition implements Condition { | |
@Override | |
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { | |
if (context.getRegistry().containsBeanDefinition("ethereumService")) { | |
return false; | |
} | |
return true; | |
} | |
} | |
} |
How can various dependencies be obtained。 suach as:logger.info("Raw transaction: 0x{}", Hex.encodeHexString(tx.getEncoded()));
How to get Hex Depend on the package
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
new Transaction(getEncodedSignedRaw(tx)).verify();
What's the use of this line of code?