readme
定时任务扫描区块逻辑:
从redis获取已经同步的区块号 → 获取最新区块号 → 判断最新区块是否 > 已经同步的区块 + 1 → 大于则说明已经有完整的新区块可以进行扫描 → 扫描区块 → 判断是代币交易还是eth交易 → 如果是代币交易判断是否是转账操作(因为会有一些发币/授权/发合约…这种我们不需要的交易) → 判断to地址是否是系统内账户的(看具体项目 做可导入钱包那种项目就忽略这一步) → 计算交易金额 → 自行进行逻辑处理 → 保存刚同步的区块号到redis
返回数据的讲解:
先看一下api接口返回的数据结构
一般我们需要的字段hash input from to value
特别注意input 和 value字段
to是就是交易接收方的地址 代币交易时则为对应的合约地址
value是交易的eth金额 当交易为代币交易时value为0x0
input反过来 eth交易时input为0x 代币交易时是一个138位的十六进制编码 这个编码由三部分组成
1-34位为交易函数编码 例如transfer approve transferFrom等
35-74位为代币交易的实际的to地址 需要在前边拼接0x
75-138位是代币交易的十六进制的金额
transfer的十六进制编码为 0xa9059cbb
其他参考这里
ok明白这里接下来就很简单了
上代码
@Resource
private IRedisService redisService;
/**
* 扫描区块定时任务--检查到账
*/
@Scheduled(cron="0/10 * * * * ?")
public void task(){
System.out.println("定时任务开始");
try {
//获取当前同步的区块号
String block_re = redisService.getString(EthConstant.ETH_BLOCK_NUM);
//获取最新区块号
BigInteger block_now = this.getBlockNum();
//初始化
if(block_re == null){
redisService.setString(EthConstant.ETH_BLOCK_NUM, block_now.toString());
return ;
}
BigInteger block = new BigInteger(block_re).add(new BigInteger("1"));
if( block_now.compareTo(block) == 1 ){
//有新区块
List<JSONObject> list = this.getBlock(block.toString());
for (JSONObject json: list) {
//交易from地址
String from = json.getString("from");
//交易的to地址
String to = json.getString("to");
String input = json.getString("input");
if(input.length() > 30){
//确定为代币交易
if(input.substring(0, 10).equals("0xa9059cbb")){
//确定是代币转账操作 那么to就是合约地址了
//代币交易to地址
String receive = input.substring(34, 40);
receive = "0x" + receive;
//交易hash
String hash = json.getString("hash")
//交易数量 已转为10进制的 目前单位的wei
BigDecimal num = new BigDecimal(new BigInteger(input.substring(75), 16));
//合约地址 交易数量 收款账户 出账账户 交易hash 需要的信息都已拿到 剩下自行处理
}
} else {
//eth交易
EthAccountEntity account = accountService.getAccount(to);
if(account != null){
//是系统内账户
String hash = json.getString("hash");
//交易数量单位是wei
BigDecimal num = new BigDecimal(new BigInteger(json.getString("value"), 16));
//交易数量单位是ether
BigDecimal numStr = Convert.fromWei(num, Convert.Unit.ETHER);
//交易数量 收款账户 出账账户 交易hash 需要的信息都已拿到 剩下自行处理
}
}
}
redisService.setString(EthConstant.ETH_BLOCK_NUM, block.toString());
}
} catch (Exception e){
log.info("定时扫描出口异常", e);
}
}
/**
* 获取当前区块号
* @return
* @throws Exception
*/
public BigInteger getBlockNum() throws Exception {
StringBuffer path = new StringBuffer("/api?module=proxy&action=eth_blockNumber&apikey=").append(EthConstant.ETHERSCAN_API_KEY);
HttpResponse response = HttpUtil.doGet("https://api.etherscan.io", path.toString(), "GET", headers, querys);
String res = EntityUtils.toString(response.getEntity());
JSONObject json = JSONObject.parseObject(res);
String result = json.getString("result");
return new BigInteger(result.substring(2), 16);
}
/**
* 查询区块交易
* @param blockNum
* @return
* @throws Exception
*/
public List<JSONObject> getBlock(String blockNum) throws Exception {
StringBuffer path = new StringBuffer("/api?module=proxy&action=eth_getBlockByNumber&tag=").append(blockNum)
.append("&boolean=true&apikey=").append(EthConstant.ETHERSCAN_API_KEY);
HttpResponse response = HttpUtil.doGet("https://api.etherscan.io", path.toString(), "GET", headers, querys);
String res = EntityUtils.toString(response.getEntity());
JSONObject json = JSONObject.parseObject(res);
JSONObject result = json.getJSONObject("result");
return JSON.parseArray(result.getString("transactions"), JSONObject.class);
}
工具类
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtil {
public static HttpResponse doGet(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
Map<String, String> bodys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}
return httpClient.execute(request);
}
private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (0 < sbQuery.length()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
}
}
}
if (0 < sbQuery.length()) {
sbUrl.append("?").append(sbQuery);
}
}
return sbUrl.toString();
}
private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}
return httpClient;
}
private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (KeyManagementException ex) {
throw new RuntimeException(ex);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
}