使用Geth搭建以太坊私链环境并和手机app联调交易

2018/07/04 note 共 21541 字,约 62 分钟

参考资料

搭建私链

编写创世文件

在要存放私链数据的目录(比如E:\ligl\ethereum\geth\privateChain)下新建genesis.json创世文件

{  
  "config": {  
        "chainId": 14,  
        "homesteadBlock": 0,  
        "eip155Block": 0,  
        "eip158Block": 0  
    },  
  "alloc"      : {},  
  "coinbase"   : "0x0000000000000000000000000000000000000000",  
  "difficulty" : "0x0200",  
  "extraData"  : "",  
  "gasLimit"   : "0x2fefd8",  
  "nonce"      : "0x0000000000000042",  
  "mixhash"    : "0x0000000000000000000000000000000000000000000000000000000000000000",  
  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",  
  "timestamp"  : "0x00"  
}  

初始化

使用指令geth --datadir E:\ligl\ethereum\geth\privateChain init genesis.json可以初始化私链环境,目录下会生成gethkeystore两个文件夹,其中keystore文件夹里面存放的是地址以及加密的私钥组成的keystore文件。
为了方便反复测试,可以在当前目录新建一个init.bat批处理文件如下:

rd/s/q geth  
::不删除keystore文件夹,否则密钥需要重新导入  
::rd/s/q keystore   
geth --datadir E:\ligl\ethereum\geth\privateChain init genesis.json  

以后只要双击就可以初始化私链环境。

启动

在当前目录下新建run.bat批处理文件如下:

geth --networkid 14 --nodiscover --datadir "E:\ligl\ethereum\geth\privateChain" --rpc --rpcapi "net,eth,web3,personal" --rpcaddr "10.0.0.154" --rpcport "8545" --rpccorsdomain "*" console  

双击运行run.bat文件即可启动私链。
注意:为了外边可以访问到,rpc服务的ip地址需要设置成本机的ip地址,这样手机端app才能通过ip地址访问到rpc服务。

联调测试交易

手机端APP如何访问到服务端

为了方便,可以将服务端搭建在笔记本电脑上,然后让笔记本电脑开启wifi热点,手机连上热点即可访问服务端的JSON-RPC接口。
如何在win7的笔记本电脑上开启wifi热点

访问服务端需要使用到JSON-RPC接口,这里使用web3j库来实现:

implementation 'org.web3j:core:3.3.1-android'  

生成两个ECC的密钥对并导入到私链中

因为Geth服务貌似不支持私钥导出,所以换一种方法,先在外边生成私钥,然后导入到私链中。
使用web3j在手机端来生成私钥代码:

// log test  
logBuffer.setLength(0);  
try {  
    ECKeyPair ecKeyPair1 = Keys.createEcKeyPair();  
    ECKeyPair ecKeyPair2 = Keys.createEcKeyPair();  
    String address1 = Keys.getAddress(ecKeyPair1);  
    String address2 = Keys.getAddress(ecKeyPair2);  
    logBuffer.append("address1=").append(address1);  
    logBuffer.append("\n(1) pri=").append(ecKeyPair1.getPrivateKey().toString(16));  
    logBuffer.append("\n(1) pub=").append(ecKeyPair1.getPublicKey().toString(16));  
    logBuffer.append("\naddress2=").append(address2);  
    logBuffer.append("\n(2) pri=").append(ecKeyPair2.getPrivateKey().toString(16));  
    logBuffer.append("\n(2) pub=").append(ecKeyPair2.getPublicKey().toString(16));  
} catch (Exception e) {  
    e.printStackTrace();  
    logBuffer.append("生成地址error:").append(e);  
}  
Log.i("test", logBuffer.toString());  
showDialogMsg(logBuffer.toString());  

运行的log如下:

address1=5378ad743b9263587eb117b0c4658cf03c906baa  
(1) pri=d36d0bb6c8f2ec5e3ea59b0b8a17fd78e2091c9e77d2a70267c01cf10a38fc48  
(1) pub=34a1d310e41a8b4de639ff48d8edea8702c654207fe93783cede051f261e42e8e33d5180bdd176b45d9dd97120b7b882bd9450322fb9b04ef31cb285a72f347c  
  
  
address2=370dccebbf4d24059ae57fddbb651259a86a172a  
(2) pri=d90b9e5b063cd5d8e56c536c341260048f563c5e8740e071145e72e4208bf883  
(2) pub=ece19a85208e5198a412ab2710a992356d837726db7e06d745d31d667339e923e92409c00d17b969545a2557b8cd3869e4fa4d90c3ca23d58c693c34247a1dab  

新建一个prv1.txt文件,将私钥的hex串复制进去保存,然后在服务端的console中使用指令:
geth --datadir E:\ligl\ethereum\geth\privateChain account import E:\ligl\ethereum\geth\privateChain\test_key\prv1.txt
即可导入第一个地址,同理再导入第二个地址。

通过挖矿给第一个地址加钱

在服务端使用指令eth.coinbase可以获取挖矿的收益地址,第一次运行会默认将上面导入的第一个地址当做挖矿收益地址。
然后使用命令miner.start(1)启动挖矿,因为创世文件设置的难度特别低,所以很快就能挖到大量的以太币。
使用命令miner.stop()命令停止挖矿。

编写APP端JSON-RPC的访问代码

手机端测试需要访问JSON-RPC接口,这里使用web3j库来访问。
根据myetherwallet网站开源代码中的接口(接口点这里),模仿写一个java的接口:

EtherNode.java

import java.math.BigInteger;  
  
import rx.Observable;  
  
/**  
 * 根据myetherwallet.com定义的api接口定义  
 * 用来实现以太坊的交易以及合同的发布时的网络接口  
 */  
public interface EtherNode {  
    /**  
     * 获取ChainId,用于EIP155签名时用  
     * @return chainId  
     */  
    int getChainId();  
    /**  
     * // Request  
     * curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}'  
     *  
     * // Result  
     * {  
     * "id":83,  
     * "jsonrpc": "2.0",  
     * "result": "0x4b7" // 1207  
     * }  
     *  
     * Returns the number of most recent block.  
     * @return QUANTITY - integer of the current block number the client is on.  
     */  
    Observable<BigInteger> getCurrentBlock();  
  
    /**  
     * // Request  
     * curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"],"id":1}'  
     *  
     * // Result  
     * {  
     * "id":1,  
     * "jsonrpc": "2.0",  
     * "result": "0x0234c8a3397aab58" // 158972490234375000  
     * }  
     *  
     * Returns the balance of the account of given address.  
     * @param address  20 Bytes - address to check for balance.  
     * @return latest balance  
     */  
    Observable<BigInteger> getBalance(String address);  
  
    /**  
     * // Request  
     * curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"],"id":1}'  
     *  
     * // Result  
     * {  
     * "id":1,  
     * "jsonrpc":"2.0",  
     * "result": {  
     * "hash":"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b",  
     * "nonce":"0x",  
     * "blockHash": "0xbeab0aa2411b7ab17f30a99d3cb9c6ef2fc5426d6ad6fd9e2a26a6aed1d1055b",  
     * "blockNumber": "0x15df", // 5599  
     * "transactionIndex":  "0x1", // 1  
     * "from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1",  
     * "to":"0x85h43d8a49eeb85d32cf465507dd71d507100c1",  
     * "value":"0x7f110", // 520464  
     * "gas": "0x7f110", // 520464  
     * "gasPrice":"0x09184e72a000",  
     * "input":"0x603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360",  
     * }  
     * }  
     *  
     * Returns the information about a transaction requested by transaction hash.  
     *  
     * @param transactionHash 32 Bytes - hash of a transaction  
     * @return  
     */  
    Observable<org.web3j.protocol.core.methods.response.Transaction> getTransaction(String transactionHash);  
  
    class TransactionData {  
        private String address;  
        private BigInteger balance;  
        private BigInteger gasPrice;  
        private BigInteger nonce;  
  
        public TransactionData() {}  
  
        public TransactionData(String address, BigInteger balance, BigInteger gasPrice, BigInteger nonce) {  
            this.address = address;  
            this.balance = balance;  
            this.gasPrice = gasPrice;  
            this.nonce = nonce;  
        }  
  
        public String getAddress() {  
            return address;  
        }  
  
        public BigInteger getBalance() {  
            return balance;  
        }  
  
        public BigInteger getGasPrice() {  
            return gasPrice;  
        }  
  
        public BigInteger getNonce() {  
            return nonce;  
        }  
  
        @Override  
        public String toString() {  
            return "TransactionData{" +  
                    "address='" + address + '\'' +  
                    ", balance=" + balance +  
                    ", gasPrice=" + gasPrice +  
                    ", nonce=" + nonce +  
                    '}';  
        }  
    }  
  
    /**  
     * 根据MyEtherWallet网站的接口定义,返回一个地址的余额、gasPrice、nonce,看用法应该是用来生成新的交易的时候用的  
     * 可以分别调用eth_getBalance获取余额,调用eth_gasPrice获取gasPrice,调用eth_getTransactionCount获取nonce  
     *  
     * @param address 20 Bytes - address to check for balance.  
     * @return  
     */  
    Observable<TransactionData> getTransactionData(String address);  
  
    /**  
     * // Request  
     * curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}'  
     *  
     * // Result  
     * {  
     * "id":1,  
     * "jsonrpc": "2.0",  
     * "result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"  
     * }  
     *  
     * Creates new message call transaction or a contract creation for signed transactions.  
     * @param signedTransactionData  
     * @return 交易的hash值  
     */  
    Observable<String> sendRawTransaction(String signedTransactionData);  
  
    /**  
     * // Request  
     * curl -X POST --data '{"jsonrpc":"2.0","method":"eth_estimateGas","params":[{see above}],"id":1}'  
     *  
     * // Result  
     * {  
     * "id":1,  
     * "jsonrpc": "2.0",  
     * "result": "0x5208" // 21000  
     * }  
     *  
     * Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.  
     * The transaction will not be added to the blockchain.  
     * Note that the estimate may be significantly more than the amount of gas actually used by the transaction,  
     * for a variety of reasons including EVM mechanics and node performance.  
     *  
     * @param transaction  
     * @return the amount of gas used.  
     */  
    Observable<BigInteger> getEstimatedGas(org.web3j.protocol.core.methods.request.Transaction transaction);  
  
    /**  
     *  
     * // Request  
     * curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{see above}],"id":1}'  
     *  
     * // Result  
     * {  
     * "id":1,  
     * "jsonrpc": "2.0",  
     * "result": "0x"  
     * }  
     *  
     * Executes a new message call immediately without creating a transaction on the block chain.  
     * param use 'pending'  
     * see <a href='https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_call'>https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_call</a>  
     * @param transaction The transaction call object  
     * @return the return value of executed contract.  
     */  
    Observable<String> getEthCall(org.web3j.protocol.core.methods.request.Transaction transaction);  
}  

然后写一个实现CustomNodeImpl.java:

import android.util.Log;  
  
import com.hengbao.hdwallet.network.ether.EtherNode;  
  
import org.web3j.protocol.Web3j;  
import org.web3j.protocol.Web3jFactory;  
import org.web3j.protocol.core.DefaultBlockParameterName;  
import org.web3j.protocol.core.methods.response.EthBlock;  
import org.web3j.protocol.core.methods.response.EthCall;  
import org.web3j.protocol.core.methods.response.EthEstimateGas;  
import org.web3j.protocol.core.methods.response.EthGasPrice;  
import org.web3j.protocol.core.methods.response.EthGetBalance;  
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;  
import org.web3j.protocol.core.methods.response.EthSendTransaction;  
import org.web3j.protocol.core.methods.response.EthTransaction;  
import org.web3j.protocol.core.methods.response.Transaction;  
import org.web3j.protocol.http.HttpService;  
  
import java.math.BigInteger;  
  
import okhttp3.OkHttpClient;  
import okhttp3.logging.HttpLoggingInterceptor;  
import rx.Observable;  
import rx.exceptions.Exceptions;  
import rx.functions.Func1;  
import rx.functions.Func3;  
  
/**  
 * 符合web3j的JSON-RPC接口的节点实现  
 */  
public class CustomNodeImpl implements EtherNode {  
    /**  
     * chainId,see {@link org.web3j.tx.ChainId}  
     */  
    private int chainId;  
    /**  
     * 服务器的url,ex:http://localhost:8545/  
     */  
    private String url;  
    private Web3j web3j;  
  
    /**  
     * 构造  
     * @param url 服务器的url,ex:http://localhost:8545/  
     * @param chainId chainId,see {@link org.web3j.tx.ChainId}  
     */  
    public CustomNodeImpl(String url, int chainId) {  
        this.url = url;  
        this.chainId = chainId;  
  
        OkHttpClient.Builder builder = new OkHttpClient.Builder();  
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor(  
                new HttpLoggingInterceptor.Logger() {  
                    @Override  
                    public void log(String msg) {  
                        Log.i("http", msg);  
                    }  
                });  
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);  
        builder.addInterceptor(logging);  
  
        this.web3j = Web3jFactory.build(new HttpService(url, builder.build(), true));  
    }  
  
    @Override  
    public Observable<BigInteger> getCurrentBlock() {  
        return web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, true)  
                .observable()  
                .map(new Func1<EthBlock, BigInteger>() {  
                    @Override  
                    public BigInteger call(EthBlock ethBlock) {  
                        if(ethBlock.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethBlock.getError()));  
                        } else {  
                            return ethBlock.getBlock().getNumber();  
                        }  
                    }  
                });  
    }  
  
    @Override  
    public Observable<BigInteger> getBalance(String address) {  
        return web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST)  
                .observable()  
                .map(new Func1<EthGetBalance, BigInteger>() {  
                    @Override  
                    public BigInteger call(EthGetBalance ethGetBalance) {  
                        if(ethGetBalance.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethGetBalance.getError()));  
                        } else {  
                            return ethGetBalance.getBalance();  
                        }  
                    }  
                });  
    }  
  
    @Override  
    public Observable<Transaction> getTransaction(String transactionHash) {  
        return web3j.ethGetTransactionByHash(transactionHash)  
                .observable()  
                .map(new Func1<EthTransaction, Transaction>() {  
                    @Override  
                    public Transaction call(EthTransaction ethTransaction) {  
                        if(ethTransaction.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethTransaction.getError()));  
                        } else {  
                            return ethTransaction.getTransaction();  
                        }  
                    }  
                });  
    }  
  
    @Override  
    public Observable<TransactionData> getTransactionData(final String address) {  
        Observable<BigInteger> balanceObservable = web3j.ethGetBalance(address, DefaultBlockParameterName.PENDING)  
                .observable()  
                .map(new Func1<EthGetBalance, BigInteger>() {  
                    @Override  
                    public BigInteger call(EthGetBalance ethGetBalance) {  
                        if(ethGetBalance.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethGetBalance.getError()));  
                        } else {  
                            return ethGetBalance.getBalance();  
                        }  
                    }  
                });  
        Observable<BigInteger> gasPriceObservable = web3j.ethGasPrice()  
                .observable()  
                .map(new Func1<EthGasPrice, BigInteger>() {  
                    @Override  
                    public BigInteger call(EthGasPrice ethGasPrice) {  
                        if(ethGasPrice.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethGasPrice.getError()));  
                        } else {  
                            return ethGasPrice.getGasPrice();  
                        }  
                    }  
                });  
        Observable<BigInteger> nonceObservable = web3j.ethGetTransactionCount(address, DefaultBlockParameterName.PENDING)  
                .observable()  
                .map(new Func1<EthGetTransactionCount, BigInteger>() {  
                    @Override  
                    public BigInteger call(EthGetTransactionCount ethGetTransactionCount) {  
                        if(ethGetTransactionCount.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethGetTransactionCount.getError()));  
                        } else {  
                            return ethGetTransactionCount.getTransactionCount();  
                        }  
                    }  
                });  
        return Observable.zip(balanceObservable, gasPriceObservable, nonceObservable,  
                new Func3<BigInteger, BigInteger, BigInteger, TransactionData>() {  
                    @Override  
                    public TransactionData call(BigInteger balance, BigInteger gasPrice, BigInteger nonce) {  
                        return new TransactionData(address, balance, gasPrice, nonce);  
                    }  
                });  
    }  
  
    @Override  
    public Observable<String> sendRawTransaction(String signedTransactionData) {  
        return web3j.ethSendRawTransaction(signedTransactionData)  
                .observable()  
                .map(new Func1<EthSendTransaction, String>() {  
                    @Override  
                    public String call(EthSendTransaction ethSendTransaction) {  
                        if(ethSendTransaction.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethSendTransaction.getError()));  
                        } else {  
                            return ethSendTransaction.getTransactionHash();  
                        }  
                    }  
                });  
    }  
  
    @Override  
    public Observable<BigInteger> getEstimatedGas(org.web3j.protocol.core.methods.request.Transaction transaction) {  
        return web3j.ethEstimateGas(transaction)  
                .observable()  
                .map(new Func1<EthEstimateGas, BigInteger>() {  
                    @Override  
                    public BigInteger call(EthEstimateGas ethEstimateGas) {  
                        if(ethEstimateGas.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethEstimateGas.getError()));  
                        } else {  
                            return ethEstimateGas.getAmountUsed();  
                        }  
                    }  
                });  
    }  
  
    @Override  
    public Observable<String> getEthCall(org.web3j.protocol.core.methods.request.Transaction transaction) {  
        return web3j.ethCall(transaction, DefaultBlockParameterName.PENDING)  
                .observable()  
                .map(new Func1<EthCall, String>() {  
                    @Override  
                    public String call(EthCall ethCall) {  
                        if(ethCall.hasError()) {  
                            throw Exceptions.propagate(JsonRpcException.crate(ethCall.getError()));  
                        } else {  
                            return ethCall.getValue();  
                        }  
                    }  
                });  
    }  
  
    @Override  
    public int getChainId() {  
        return chainId;  
    }  
  
    public String getUrl() {  
        return url;  
    }  
}  

APP端查询余额

查询地址1余额:

final String address1 = "0x5378ad743b9263587eb117b0c4658cf03c906baa";  
...省略部分代码...  
etherNode.getBalance(address1)  
        .subscribeOn(Schedulers.newThread())  
        .observeOn(AndroidSchedulers.mainThread())  
        .subscribe(new Action1<BigInteger>() {  
            @Override  
            public void call(BigInteger bigInteger) {  
                showDialogMsg(String.format("地址%s的余额为:%s(wei)", address1, bigInteger.toString()));  
            }  
        }, new Action1<Throwable>() {  
            @Override  
            public void call(Throwable throwable) {  
                throwable.printStackTrace();  
                showDialogMsg("查询余额失败:" + throwable);  
            }  
        });  

查询地址2余额:

final String address2 = "0x370dccebbf4d24059ae57fddbb651259a86a172a";  
...省略部分代码...  
etherNode.getBalance(address2)  
        .subscribeOn(Schedulers.newThread())  
        .observeOn(AndroidSchedulers.mainThread())  
        .subscribe(new Action1<BigInteger>() {  
            @Override  
            public void call(BigInteger bigInteger) {  
                showDialogMsg(String.format("地址%s的余额为:%s(wei)", address2, bigInteger.toString()));  
            }  
        }, new Action1<Throwable>() {  
            @Override  
            public void call(Throwable throwable) {  
                throwable.printStackTrace();  
                showDialogMsg("查询余额失败:" + throwable);  
            }  
        });  

运行截图:
yue1

APP端转账

转账代码:

// 两个地址的测试数据  
final String address1 = "0x5378ad743b9263587eb117b0c4658cf03c906baa";  
final ECKeyPair ecKeyPair1 = new ECKeyPair(  
        new BigInteger("d36d0bb6c8f2ec5e3ea59b0b8a17fd78e2091c9e77d2a70267c01cf10a38fc48", 16),  
        new BigInteger("34a1d310e41a8b4de639ff48d8edea8702c654207fe93783cede051f261e42e8e33d5180bdd176b45d9dd97120b7b882bd9450322fb9b04ef31cb285a72f347c", 16));  
  
final String address2 = "0x370dccebbf4d24059ae57fddbb651259a86a172a";  
final ECKeyPair ecKeyPair2 = new ECKeyPair(  
        new BigInteger("d90b9e5b063cd5d8e56c536c341260048f563c5e8740e071145e72e4208bf883", 16),  
        new BigInteger("ece19a85208e5198a412ab2710a992356d837726db7e06d745d31d667339e923e92409c00d17b969545a2557b8cd3869e4fa4d90c3ca23d58c693c34247a1dab", 16));  
  
...省略部分代码...  
  
// 清空log buffer  
logBuffer.setLength(0);  
  
final BigInteger amount = new BigInteger("9000");  
Observable<BigInteger> currBlockObs = etherNode.getCurrentBlock();  
org.web3j.protocol.core.methods.request.Transaction estimatedGasTran =  
        new org.web3j.protocol.core.methods.request.Transaction(  
                address1,  
                null, // nonce  
                new BigInteger("051da038cc", 16), // gasPrice  
                new BigInteger("ffffff", 16), // gasLimit  
                address2, // to  
                amount, // value  
                null);  
Observable<BigInteger> estimatedGasObs = etherNode.getEstimatedGas(estimatedGasTran);  
Observable<EtherNode.TransactionData> transactionDataObs = etherNode.getTransactionData(address1);  
  
Observable.zip(currBlockObs, estimatedGasObs, transactionDataObs, new Func3<BigInteger ,BigInteger, EtherNode.TransactionData, RawTransaction>() {  
    @Override  
    public RawTransaction call(BigInteger currBlock, BigInteger gas_limit, EtherNode.TransactionData transactionData) {  
        // test log  
        logBuffer.append("\n获取到的currBlock=").append(currBlock);  
        logBuffer.append("\n获取到的gas_limit=").append(gas_limit);  
        logBuffer.append("\n获取到的transactionData=").append(transactionData);  
  
        // test log  
        logBuffer.append("\n组交易之前:\nnonce=").append(transactionData.getNonce().toString(16))  
                .append("\ngasPrice=").append(transactionData.getGasPrice().toString(16))  
                .append("\ngasLimit=").append(gas_limit.toString(16))  
                .append("\ntoAddress=").append(address2)  
                .append("\namount=").append(amount.toString(16))  
                .append("\ndata=null");  
  
        // 需要为每个交易  
  
        RawTransaction tx = RawTransaction.createTransaction(  
                transactionData.getNonce(),  
                transactionData.getGasPrice(),  
                gas_limit,  
                address2,  
                amount,  
                ""  
        );  
        return tx;  
    }  
}).map(new Func1<RawTransaction, String>() {  
    @Override  
    public String call(RawTransaction rawTransaction) {  
        // TODO 直接使用EIP155的规范进行签名,会不会出现问题?  
        Credentials keys = Credentials.create(ecKeyPair1);  
        byte[] signed = TransactionEncoder.signMessage(rawTransaction, (byte) etherNode.getChainId(), keys);  
        String signTx = ByteUtils.toHexString(signed);  
        logBuffer.append("\n签名以后的数据为:").append(signTx);  
        return signTx;  
    }  
}).flatMap(new Func1<String, Observable<String>>() {  
    @Override  
    public Observable<String> call(String tx) {  
        logBuffer.append("\n调用发布交易接口...");  
        if(tx != null && !tx.startsWith("0x") && !tx.startsWith("0X")) {  
            tx = "0x" + tx;  
        }  
        return etherNode.sendRawTransaction(tx);  
    }  
}).subscribeOn(Schedulers.newThread())  
.observeOn(AndroidSchedulers.mainThread())  
.subscribe(new Action1<String>() {  
    @Override  
    public void call(String s) {  
        logBuffer.append("\n交易发布成功,hash为:").append(s);  
  
        showDialogMsg(logBuffer.toString());  
        Log.i("test", logBuffer.toString());  
    }  
}, new Action1<Throwable>() {  
    @Override  
    public void call(Throwable throwable) {  
        throwable.printStackTrace();  
        logBuffer.append("\n交易发布失败,throwable=").append(throwable);  
        showDialogMsg(logBuffer.toString());  
        Log.i("test", logBuffer.toString());  
    }  
});  

注意:为了转账的交易得到确认,需要开启挖矿,让交易被确认,这样地址二查询余额才能看到变化。

转账代码中,logBuffer部分的log:

获取到的gas_limit=21000  
获取到的transactionData=TransactionData{address='0x5378ad743b9263587eb117b0c4658cf03c906baa', balance=59999999999999991000, gasPrice=18000000000, nonce=1}  
组交易之前:  
nonce=1  
gasPrice=430e23400  
gasLimit=5208  
toAddress=0x370dccebbf4d24059ae57fddbb651259a86a172a  
amount=2328  
data=null  
签名以后的数据为:f86601850430e2340082520894370dccebbf4d24059ae57fddbb651259a86a172a8223288040a003877e44f1d4777d668f37a0332159c40b7ad1b7fb67a782371286f4de973fb7a065d480bb839347a1ffcb7eef61f41e9c78cf0c37102a892d7d7c2485ac9ac23b  
调用发布交易接口...  
交易发布成功,hash为:0x47bd8331440bb31d8a6d5e27a7cf2a0a357f10ed911959549ccc0d3ef69d8a9b  

OKHttp访问JSON-RPC接口的log:

07-04 11:45:23.861 20334-20564/com.hengbao.hdwallet I/http: --> POST http://10.0.0.154:8545/ http/1.1  
07-04 11:45:23.871 20334-20564/com.hengbao.hdwallet I/http: Content-Type: application/json; charset=utf-8  
    Content-Length: 225  
    {"jsonrpc":"2.0","method":"eth_estimateGas","params":[{"from":"0x5378ad743b9263587eb117b0c4658cf03c906baa","gas":"0xffffff","gasPrice":"0x51da038cc","to":"0x370dccebbf4d24059ae57fddbb651259a86a172a","value":"0x2328"}],"id":0}  
    --> END POST (225-byte body)  
07-04 11:45:23.891 20334-20564/com.hengbao.hdwallet I/http: <-- 200 OK http://10.0.0.154:8545/ (26ms)  
    Content-Type: application/json  
    Vary: Origin  
    Date: Wed, 04 Jul 2018 03:47:05 GMT  
    Content-Length: 43  
    {"jsonrpc":"2.0","id":0,"result":"0x5208"}  
    <-- END HTTP (43-byte body)  
07-04 11:45:23.921 20334-20564/com.hengbao.hdwallet I/http: --> POST http://10.0.0.154:8545/ http/1.1  
    Content-Type: application/json; charset=utf-8  
    Content-Length: 116  
    {"jsonrpc":"2.0","method":"eth_getBalance","params":["0x5378ad743b9263587eb117b0c4658cf03c906baa","pending"],"id":1}  
    --> END POST (116-byte body)  
07-04 11:45:23.931 20334-20564/com.hengbao.hdwallet I/http: <-- 200 OK http://10.0.0.154:8545/ (10ms)  
    Content-Type: application/json  
    Vary: Origin  
    Date: Wed, 04 Jul 2018 03:47:05 GMT  
    Content-Length: 56  
    {"jsonrpc":"2.0","id":1,"result":"0x340aad21b3b6fdcd8"}  
    <-- END HTTP (56-byte body)  
07-04 11:45:23.941 20334-20564/com.hengbao.hdwallet I/http: --> POST http://10.0.0.154:8545/ http/1.1  
    Content-Type: application/json; charset=utf-8  
    Content-Length: 60  
    {"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":2}  
    --> END POST (60-byte body)  
07-04 11:45:23.951 20334-20564/com.hengbao.hdwallet I/http: <-- 200 OK http://10.0.0.154:8545/ (9ms)  
    Content-Type: application/json  
    Vary: Origin  
    Date: Wed, 04 Jul 2018 03:47:05 GMT  
    Content-Length: 48  
    {"jsonrpc":"2.0","id":2,"result":"0x430e23400"}  
    <-- END HTTP (48-byte body)  
    --> POST http://10.0.0.154:8545/ http/1.1  
    Content-Type: application/json; charset=utf-8  
    Content-Length: 125  
07-04 11:45:23.961 20334-20564/com.hengbao.hdwallet I/http: {"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x5378ad743b9263587eb117b0c4658cf03c906baa","pending"],"id":3}  
    --> END POST (125-byte body)  
07-04 11:45:23.981 20334-20564/com.hengbao.hdwallet I/http: <-- 200 OK http://10.0.0.154:8545/ (23ms)  
    Content-Type: application/json  
    Vary: Origin  
    Date: Wed, 04 Jul 2018 03:47:05 GMT  
    Content-Length: 40  
    {"jsonrpc":"2.0","id":3,"result":"0x1"}  
    <-- END HTTP (40-byte body)  
07-04 11:45:24.101 20334-20564/com.hengbao.hdwallet I/http: --> POST http://10.0.0.154:8545/ http/1.1  
    Content-Type: application/json; charset=utf-8  
    Content-Length: 282  
    {"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf86601850430e2340082520894370dccebbf4d24059ae57fddbb651259a86a172a8223288040a003877e44f1d4777d668f37a0332159c40b7ad1b7fb67a782371286f4de973fb7a065d480bb839347a1ffcb7eef61f41e9c78cf0c37102a892d7d7c2485ac9ac23b"],"id":4}  
    --> END POST (282-byte body)  
07-04 11:45:24.141 20334-20564/com.hengbao.hdwallet I/http: <-- 200 OK http://10.0.0.154:8545/ (34ms)  
    Content-Type: application/json  
    Vary: Origin  
    Date: Wed, 04 Jul 2018 03:47:05 GMT  
    Content-Length: 103  
    {"jsonrpc":"2.0","id":4,"result":"0x47bd8331440bb31d8a6d5e27a7cf2a0a357f10ed911959549ccc0d3ef69d8a9b"}  
    <-- END HTTP (103-byte body)  

文档信息

Search

    Table of Contents