分类 开发文档 下的文章 - 六币之门
首页
视频教程
网站导航
活动日历
关于我们
用户投稿
推荐
新闻动态
搜 索
1
融资周报 | 公开融资事件11起;加密技术公司Toposware完成500万美元融资,Polygon联创参投
107 阅读
2
六币日报 | 九只比特币ETF在6天内积累了9.5万枚BTC;贝莱德决定停止推出XRP现货ETF计划
73 阅读
3
六币日报 | 美国SEC再次推迟对灰度以太坊期货ETF做出决定;Do Kwon已出黑山监狱等待引渡
68 阅读
4
融资周报 | 公开融资事件27起;L1区块链Monad Labs完成2.25亿美元融资,Paradigm领投
67 阅读
5
【ETH钱包开发06】查询某个地址的交易记录
43 阅读
新闻动态
每日快报
一周精选
融资情况
项目投研
自治组织
数字藏品
去中心化应用
去中心化游戏
去中心化社交
去中心化金融
区块链交易所
科普知识
小白入门
用户手册
开发文档
行业报告
技术前沿
登录
搜 索
标签搜索
新闻
日报
元歌Eden
累计撰写
1,087
篇文章
累计收到
0
条评论
首页
栏目
新闻动态
每日快报
一周精选
融资情况
项目投研
自治组织
数字藏品
去中心化应用
去中心化游戏
去中心化社交
去中心化金融
区块链交易所
科普知识
小白入门
用户手册
开发文档
行业报告
技术前沿
页面
视频教程
网站导航
活动日历
关于我们
用户投稿
推荐
新闻动态
用户登录
登录
找到
27
篇与
开发文档
相关的结果
2023-03-03
【ETH钱包开发01】创建、导出钱包
简介这篇文章主要是介绍ETH移动端(Android)钱包开发,核心功能包括创建钱包、导入钱包、钱包转账(收款)、交易查询等。关于钱包的基本概念钱包地址以0x开头的42位的哈希值 (16进制) 字符串keystore明文私钥通过加密算法加密过后的 JSON 格式的字符串, 一般以文件形式存储助记词12 (或者 15、18、21) 单词构成, 用户可以通过助记词导入钱包, 但反过来讲, 如果他人得到了你的助记词, 不需要任何密码就可以轻而易举的转移你的资产, 所以要妥善保管自己的助记词明文私钥64位的16进制哈希值字符串, 用一句话阐述明文私钥的重要性 “谁掌握了私钥, 谁就掌握了该钱包的使用权!” 同样, 如果他人得到了你的明文私钥, 不需要任何密码就可以轻而易举的转移你的资产和银行卡做个简单类比地址=银行卡号密码=银行卡密码私钥=银行卡号+银行卡密码助记词=银行卡号+银行卡密码Keystore+密码=银行卡号+银行卡密码Keystore ≠ 银行卡号私钥通过椭圆曲线签名得到公钥 ,公钥经过哈希得到钱包地址 ,整个过程单向的(不可逆 )私钥------->公钥------->钱包地址关于BIP协议这里先简单介绍一下BIP,后面再单独出一篇文章讲解。BIP协议是比特币的一个改进协议,在钱包开发中主要用到BIP32、BIP39、BIP44BIP32:定义了层级确定性钱包( Hierarchical Deterministic wallet ,简称 HD Wallet),是一个系统可以从单一个 seed 产生一树状结构储存多组 keypairs(私钥和公钥)。好处是可以方便的备份、转移到其他相容装置(因为都只需要 seed),以及分层的权限控制等。BIP39:用于生成助记词,将 seed 用方便记忆和书写的单词表示,一般由 12 个单字组成,单词列表总共有2048个单词。WordlistsBIP44:基于 BIP32 的系统,赋予树状结构中的各层特殊的意义。让同一个 seed 可以支援多币种、多帐户等。各层定义如下:m / purpose' / coin_type' / account' / change / address_indexpurporse': 固定值44', 代表是BIP44 coin_type': 这个代表的是币种, 可以兼容很多种币, 比如BTC是0', ETH是60',btc一般是 m/44'/0'/0'/0,eth一般是 m/44'/60'/0'/0 account’:账号 change’: 0表示外部链(External Chain),用户接收比特币,1表示内部链(Internal Chain),用于接收找零 address_index:钱包索引 准备工具eth钱包开发需要借助2个第三方库web3j:可以理解为eth API的java版本bitcoinj:生成支持bip32和bip44的钱包。还有其他的一些库支持bip32和bip44,比如:Nova Crypto的系列包,包含bip32,bip39,bip44,我就是使用的Nova Crypto系列包。注意:因为web3j不支持生成bip44的钱包,而市面上大多数钱包使用bip32,bip39,bip44标准结合生成,所以引用此包。在创建完钱包之后,你可以使用下面这个工具去测试助记词, 和校验助记词生成的地址、公钥、私钥等。https://iancoleman.io/bip39/创建钱包在了解BIP 后,我们开始以太坊钱包开发,创建的钱包的流程为:1、随机生成一组助记词2、生成 seed3、生成 master key4、生成 child key5、我们取第一组child key即m/44'/60'/0'/0/0 得到私钥,keystore及地址1、引用库:web3j implementation 'org.web3j:core:3.3.1-android' 创建钱包相关全家桶的那个bip32有点问题,用我这里给出的那个// implementation 'org.bitcoinj:bitcoinj-core:0.14.7' implementation 'io.github.novacrypto:BIP39:0.1.9' //用于生成助记词 implementation 'io.github.novacrypto:BIP44:0.0.3' // implementation 'io.github.novacrypto:BIP32:0.0.9' //使用这个bip32 implementation 'com.lhalcyon:bip32:1.0.0' implementation 'com.lambdaworks:scrypt:1.4.0' //加密算法 2、生成随机助记词 /** * generate a random group of mnemonics * 生成一组随机的助记词 */ public String generateMnemonics() { StringBuilder sb = new StringBuilder(); byte[] entropy = new byte[Words.TWELVE.byteLength()]; new SecureRandom().nextBytes(entropy); new MnemonicGenerator(English.INSTANCE) .createMnemonic(entropy, sb::append); return sb.toString(); } 3. 根据助记词计算出Seed,得到master key ,根据BIP44派生地址,获取KeyPair/** * generate key pair to create eth wallet_normal * 生成KeyPair , 用于创建钱包(助记词生成私钥) */ public ECKeyPair generateKeyPair(String mnemonics) { // 1. we just need eth wallet_normal for now AddressIndex addressIndex = BIP44 .m() .purpose44() .coinType(60) .account(0) .external() .address(0); // 2. calculate seed from mnemonics , then get master/root key ; Note that the bip39 passphrase we set "" for common byte[] seed = new SeedCalculator().calculateSeed(mnemonics, ""); ExtendedPrivateKey rootKey = ExtendedPrivateKey.fromSeed(seed, Bitcoin.MAIN_NET); Log.i(TAG, "mnemonics:" + mnemonics); String extendedBase58 = rootKey.extendedBase58(); Log.i(TAG, "extendedBase58:" + extendedBase58); // 3. get child private key deriving from master/root key ExtendedPrivateKey childPrivateKey = rootKey.derive(addressIndex, AddressIndex.DERIVATION); String childExtendedBase58 = childPrivateKey.extendedBase58(); Log.i(TAG, "childExtendedBase58:" + childExtendedBase58); // 4. get key pair byte[] privateKeyBytes = childPrivateKey.getKey(); ECKeyPair keyPair = ECKeyPair.create(privateKeyBytes); // we 've gotten what we need String privateKey = childPrivateKey.getPrivateKey(); String publicKey = childPrivateKey.neuter().getPublicKey(); String address = Keys.getAddress(keyPair); Log.i(TAG, "privateKey:" + privateKey); Log.i(TAG, "publicKey:" + publicKey); Log.i(TAG, "address:" + Constant.PREFIX_16 + address); return keyPair; } 这一步已经得到钱包公钥、私钥、地址了。如果需要测试助记词, 和校验助记词生成的地址, 那么可以访问这个网站 : https://iancoleman.io/bip39/4、通过keypair创建钱包 /** * 创建钱包(助记词方式) * * @param context app context 上下文 * @param password the wallet_normal password(not the bip39 password) 钱包密码(而不是BIP39的密码) * @param mnemonics 助记词 * @param walletName 钱包名称 * @return wallet_normal 钱包 */ public Flowable<HLWallet> generateWallet(Context context, String password, String mnemonics, String walletName) { Flowable<String> flowable = Flowable.just(mnemonics); return flowable .map(s -> { ECKeyPair keyPair = generateKeyPair(s); WalletFile walletFile = Wallet.createLight(password, keyPair); HLWallet hlWallet = new HLWallet(walletFile, walletName); WalletManager.shared().saveWallet(context, hlWallet); //保存钱包信息 return hlWallet; }); } 这样生成的就是符合bip32、bip39、bip44的钱包,也能和市面上包括imtoken在内的大多数钱包通用了。HLWalletpublic class HLWallet { public WalletFile walletFile; //钱包文件,包含私钥、keystore、address等信息 public String walletName; //钱包名称 @JsonIgnore public boolean isCurrent = false; public HLWallet() { } public HLWallet(WalletFile walletFile) { this.walletFile = walletFile; } public HLWallet(WalletFile walletFile, String walletName) { this.walletFile = walletFile; this.walletName = walletName; } public String getAddress(){ return Constant.PREFIX_16 + this.walletFile.getAddress(); } public String getWalletName() { return walletName; } public void setWalletName(String walletName) { this.walletName = walletName; } } 导出钱包导出私钥通过解密获得ECKeyPair通过ECKeyPair获得私钥,并转换成16进制,就是最后的私钥了。 /** * 导出私钥 * * @param password 创建钱包时的密码 * @param walletFile * @return */ public String exportPrivateKey(String password, WalletFile walletFile) { try { // ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出现OOM ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile); String privateKey = Numeric.toHexStringNoPrefix(ecKeyPair.getPrivateKey()); return privateKey; } catch (CipherException e) { e.printStackTrace(); return "error"; } } 导出keystore /** * 导出Keystore * * @param password 创建钱包时的密码 * @param walletFile * @return */ public String exportKeystore(String password, WalletFile walletFile) { if (decrypt(password, walletFile)) { return new Gson().toJson(walletFile); } else { return "decrypt failed"; } } /** * 解密 * 如果方法没有抛出CipherException异常则表示解密成功,也就是说可以把Wallet相关信息展示给用户看 * * @param password 创建钱包时的密码 * @param walletFile * @return */ public boolean decrypt(String password, WalletFile walletFile) { try { // ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出现OOM ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile); return true; } catch (CipherException e) { e.printStackTrace(); return false; } } 注意2点:1、在导出私钥、keystore、助记词之前都需要先验证密码是否正确,也就是调用如下这个方法,如果没有抛出异常,则把信息展示给用户看。Wallet.decrypt(password, walletFile); 2、使用web3j的这个decrypt,经常会抛出OOM异常。关于解决方案,大家查看这里。https://www.jianshu.com/p/41d4a38754a3导出助记词助记词是没有办法根据私钥或者keystore推导出来的。一般的做法是在创建钱包的时候把助记词加密后在本地存储,导出时解密。注意:使用IMToken导入私钥或者KeyStore创建的钱包,没有导出助记词的功能;如果是通过助记词创建的,就会有导出助记词的功能。而且助记词一旦备份之后,备份这个功能就会消失,也就是说从本地存储中删除。 /** * 导出助记词 * * @param password * @param hlWallet * @return */ public String exportMnemonics(String password, HLWallet hlWallet) { WalletFile walletFile = hlWallet.walletFile; if (decrypt(password, walletFile)) { return hlWallet.getMnemonic(); } else { return "decrypt failed"; } }
2023年03月03日
5 阅读
0 评论
0 点赞
2023-03-03
【ETH钱包开发02】导入钱包
简介本文主要讲解通过助记词、keystore、私钥 3种方式来导入钱包。导入钱包就是说根据输入的这3者中的一个去重新生成一个新的钱包。导入钱包的过程和创建的过程其实是差不多的。根据助记词导入钱包根据助记词导入钱包不需要原始密码,密码可以重新设置。根据用户输入的助记词,先验证助记词的合规性(格式、个数等),验证正确后,配合用户输入的密码重新生成一个新的钱包。验证助记词的合规性(格式、个数等)private boolean validateInput(String mnemonics, String password, String repassword) { // validate empty if (TextUtils.isEmpty(mnemonics) || TextUtils.isEmpty(password) || TextUtils.isEmpty(repassword)) { ScheduleCompat.snackInMain(startImportBtn, "请填写助记词和密码"); return false; } // validate password if (!TextUtils.equals(password, repassword)) { ScheduleCompat.snackInMain(startImportBtn, "密码不一致"); return false; } // validate mnemonic try { MnemonicValidator.ofWordList(English.INSTANCE).validate(mnemonics); } catch (InvalidChecksumException e) { e.printStackTrace(); ScheduleCompat.snackInMain(startImportBtn, "助记词格式不正确"); return false; } catch (InvalidWordCountException e) { e.printStackTrace(); ScheduleCompat.snackInMain(startImportBtn, "请检查单词个数"); return false; } catch (WordNotFoundException e) { e.printStackTrace(); ScheduleCompat.snackInMain(startImportBtn, "无效的助记词"); return false; } catch (UnexpectedWhiteSpaceException e) { e.printStackTrace(); ScheduleCompat.snackInMain(startImportBtn, "请检查空格与分隔符"); return false; } return true; } 助记词导入钱包/** * 助记词方式导入钱包,不需要以前的密码 * * @param context * @param password * @param mnemonics 重新设置的密码 * @param walletName * @return */ public Flowable<HLWallet> importMnemonic(Context context, String password, String mnemonics, String walletName) { Flowable<String> flowable = Flowable.just(mnemonics); return flowable .flatMap(s -> { ECKeyPair keyPair = generateKeyPair(s); WalletFile walletFile = Wallet.createLight(password, keyPair); HLWallet hlWallet = new HLWallet(walletFile, walletName); if (WalletManager.shared().isWalletExist(hlWallet.getAddress())) { return Flowable.error(new HLError(ReplyCode.walletExisted, new Throwable("Wallet existed!"))); } WalletManager.shared().saveWallet(context, hlWallet); return Flowable.just(hlWallet); }); } 根据私钥导入钱包通过私钥导入钱包其实和创建钱包的过程基本一致。因为私钥在导出的时候转换成了16进制,所以在导入私钥的时候,要把16进制转换为byte数组。/** * 私钥方式导入钱包,不需要以前的密码 * * @param context * @param privateKey * @param password 重新设置的密码 * @param walletName 钱包名称 * @return */ public Flowable<HLWallet> importPrivateKey(Context context, String privateKey, String password, String walletName) { if (privateKey.startsWith(Constant.PREFIX_16)) { privateKey = privateKey.substring(Constant.PREFIX_16.length()); } Flowable<String> flowable = Flowable.just(privateKey); return flowable.flatMap(s -> { byte[] privateBytes = Hex.decode(s); ECKeyPair ecKeyPair = ECKeyPair.create(privateBytes); WalletFile walletFile = Wallet.createLight(password, ecKeyPair); HLWallet hlWallet = new HLWallet(walletFile, walletName); if (WalletManager.shared().isWalletExist(hlWallet.getAddress())) { return Flowable.error(new HLError(ReplyCode.walletExisted, new Throwable("Wallet existed!"))); } WalletManager.shared().saveWallet(context, hlWallet); return Flowable.just(hlWallet); }); } 根据Keystore导入钱包keystore就是钱包文件,实际上就是钱包信息的json字符串。导入keystore是需要输入密码的,这个密码是你最后导出keystore时的密码。将keystore字符串变成walletFile实例再通过Wallet.decrypt(password, walletFile);解密,成功则可以导入,否则不能导入。ECKeyPair keyPair = Wallet.decrypt(password, walletFile); 这是Web3j的API,程序走到这里经常OOM!具体原因的话,我就不多说了,细节大家可以看这里https://www.jianshu.com/p/41d4a38754a3解决办法根据源码修改decrypt方法,这里我用一个已经修改好的第三方库implementation 'com.lambdaworks:scrypt:1.4.0' 修改后的解密方法public static ECKeyPair decrypt(String password, WalletFile walletFile) throws CipherException { validate(walletFile); WalletFile.Crypto crypto = walletFile.getCrypto(); byte[] mac = Numeric.hexStringToByteArray(crypto.getMac()); byte[] iv = Numeric.hexStringToByteArray(crypto.getCipherparams().getIv()); byte[] cipherText = Numeric.hexStringToByteArray(crypto.getCiphertext()); byte[] derivedKey; if (crypto.getKdfparams() instanceof WalletFile.ScryptKdfParams) { WalletFile.ScryptKdfParams scryptKdfParams = (WalletFile.ScryptKdfParams) crypto.getKdfparams(); int dklen = scryptKdfParams.getDklen(); int n = scryptKdfParams.getN(); int p = scryptKdfParams.getP(); int r = scryptKdfParams.getR(); byte[] salt = Numeric.hexStringToByteArray(scryptKdfParams.getSalt()); // derivedKey = generateDerivedScryptKey(password.getBytes(Charset.forName("UTF-8")), salt, n, r, p, dklen); derivedKey = com.lambdaworks.crypto.SCrypt.scryptN(password.getBytes(Charset.forName("UTF-8")), salt, n, r, p, dklen); } else if (crypto.getKdfparams() instanceof WalletFile.Aes128CtrKdfParams) { WalletFile.Aes128CtrKdfParams aes128CtrKdfParams = (WalletFile.Aes128CtrKdfParams) crypto.getKdfparams(); int c = aes128CtrKdfParams.getC(); String prf = aes128CtrKdfParams.getPrf(); byte[] salt = Numeric.hexStringToByteArray(aes128CtrKdfParams.getSalt()); derivedKey = generateAes128CtrDerivedKey( password.getBytes(Charset.forName("UTF-8")), salt, c, prf); } else { throw new CipherException("Unable to deserialize params: " + crypto.getKdf()); } byte[] derivedMac = generateMac(derivedKey, cipherText); if (!Arrays.equals(derivedMac, mac)) { throw new CipherException("Invalid password provided"); } byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText); return ECKeyPair.create(privateKey); } 导入Kestore/** * 导入Keystore(推荐使用),Keystore+ 密码才能正确导入 * * @param context * @param keystore * @param password 以前的密码 * @param walletName * @return */ public Flowable<HLWallet> importKeystore(Context context, String keystore, String password, String walletName) { return Flowable.just(keystore) .flatMap(s -> { ObjectMapper objectMapper = new ObjectMapper(); WalletFile walletFile = objectMapper.readValue(keystore, WalletFile.class); //注意:这里用的是修改之后的解密方法 ECKeyPair keyPair = LWallet.decrypt(password, walletFile); HLWallet hlWallet = new HLWallet(walletFile, walletName); WalletFile generateWalletFile = Wallet.createLight(password, keyPair); if (!generateWalletFile.getAddress().equalsIgnoreCase(walletFile.getAddress())) { return Flowable.error(new HLError(ReplyCode.failure, new Throwable("address doesn't match private key"))); } if (WalletManager.shared().isWalletExist(hlWallet.getAddress())) { return Flowable.error(new HLError(ReplyCode.walletExisted, new Throwable("Wallet existed!"))); } WalletManager.shared().saveWallet(context, hlWallet); return Flowable.just(hlWallet); }); } 注意:1、导入助记词和私钥是不需要以前的密码的,而是重新输入新的密码;导入Keystore则需要以前的密码,如果密码不正确,会提示地址和私钥不匹配。2、关于备份助记词用过imtoken的同学可以看到imtoken是可以导出(备份)助记词的。这个一开始我也很困惑,后来了解到其实它实在创建钱包的时候,在app本地保存了助记词,导出只是讲数据读取出来而已。还有一点,imtoken一旦备份了助记词之后,之后就没有备份那个功能了,也就是说助记词在本地存储中删除了;而且导入钱包的时候也是没有备份助记词这个功能的。
2023年03月03日
8 阅读
0 评论
0 点赞
2023-03-03
【ETH钱包开发03】web3j转账ETH
在之前的文章中,讲解了创建、导出、导入钱包。【ETH钱包开发01】创建、导出钱包【ETH钱包开发02】导入钱包本文主要讲解以太坊转账相关的一些知识。交易分为ETH转账和ERC-20 Token转账,本篇先讲一下ETH转账。发起交易的2种方式1、解锁账户发起交易。钱包keyStore文件保存在geth节点上,用户发起交易需要解锁账户,适用于中心化的交易所。2、钱包文件离线签名发起交易。钱包keyStore文件保存在本地,用户使用密码+keystore的方式做离线交易签名来发起交易,适用于dapp,比如钱包。本文主要讲一下第二种方式,也就是钱包离线签名转账的方式。钱包文件签名的方式发起交易交易流程1、通过keystore加载转账所需的凭证Credentials2、创建一笔交易RawTransaction3、使用Credentials对象对交易签名4、发起交易/** * 发起一笔交易(自定义参数) * * @param from 发起人钱包地址 * @param to 转入的钱包地址 * @param value 转账金额,单位是wei * @param privateKey 钱包私钥 * @param gasPrice 转账费用 * @param gasLimit * @param data 备注的信息 * @throws IOException * @throws CipherException * @throws ExecutionException * @throws InterruptedException */ public EthSendTransaction transfer(String from, String to, BigInteger value, String privateKey, BigInteger gasPrice, BigInteger gasLimit, String data) throws IOException, CipherException, ExecutionException, InterruptedException { //加载转账所需的凭证,用私钥 Credentials credentials = Credentials.create(privateKey); //获取nonce,交易笔数 BigInteger nonce = getNonce(from); //创建RawTransaction交易对象 RawTransaction rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, to, value); //签名Transaction,这里要对交易做签名 byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signMessage); //发送交易 EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get(); return ethSendTransaction; } /** * 获取nonce,交易笔数 * * @param from * @return * @throws ExecutionException * @throws InterruptedException */ private BigInteger getNonce(String from) throws ExecutionException, InterruptedException { EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger nonce = transactionCount.getTransactionCount(); Log.i(TAG, "transfer nonce : " + nonce); return nonce; } 注意以下几点:1、Credentials这里,我是通过获取私钥的方式来加载Credentials//加载转账所需的凭证,用私钥 Credentials credentials = Credentials.create(privateKey); 还有另外一种方式,通过密码+钱包文件keystore方式来加载Credentials ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile); Credentials credentials = Credentials.create(ecKeyPair); 2、noncenonce是指发起交易的账户下的交易笔数,每一个账户nonce都是从0开始,当nonce为0的交易处理完之后,才会处理nonce为1的交易,并依次加1的交易才会被处理。可以通过eth_gettransactioncount 获取nonce3、gasPrice和gasLimit交易手续费由gasPrice 和gasLimit来决定,实际花费的交易手续费是gasUsed * gasPrice。所有这两个值你可以自定义,也可以使用系统参数获取当前两个值关于gas,你可以参考我之前的一篇文章。以太坊(ETH)GAS详解gasPrice和gasLimit影响的是转账的速度,如果gas过低,矿工会最后才打包你的交易。在app中,通常给定一个默认值,并且允许用户自己选择手续费。如果不需要自定义的话,还有一种方式来获取。获取以太坊网络最新一笔交易的gasPrice,转账的话,gasLimit一般设置为21000就可以了。/** * 获取当前以太坊网络中最近一笔交易的gasPrice */ public BigInteger requestCurrentGasPrice() throws Exception { EthGasPrice ethGasPrice = web3j.ethGasPrice().sendAsync().get(); return ethGasPrice.getGasPrice(); } Web3j还提供另外一种简单的方式来转账以太币,这种方式的好处是不需要管理nonce,不需要设置gasPrice和gasLimit,会自动获取最新一笔交易的gasPrice,gasLimit 为21000(转账一般设置成这个值就够用了)。 /** * 发起一笔交易 * 使用以太钱包文件发送以太币给其他人,不能设置nonce:(推荐) * * @param privateKey */ public String transfer(String toAddress, BigDecimal value, String privateKey) throws Exception { //转账者私钥 Credentials credentials = Credentials.create(privateKey); TransactionReceipt transactionReceipt = Transfer.sendFunds( web3j, credentials, toAddress, value, Convert.Unit.ETHER).sendAsync().get(); String transactionHash = transactionReceipt.getTransactionHash(); Log.i(TAG, "transfer: " + transactionHash); return transactionHash; } 如何验证交易是否成功?这个问题,我想是很多朋友所关心的吧。但是到目前为止,我还没有看到有讲解这方面的博客。之前问过一些朋友,他们说可以通过区块号、区块哈希来判断,也可以通过Receipt日志来判断。但是经过我的一番尝试,只有BlockHash是可行的,在web3j中根据blocknumber和transactionReceipt都会报空指针异常。原因大致是这样的:在发起一笔交易之后,会返回txHash,然后我们可以根据这个txHash去查询这笔交易相关的信息。但是刚发起交易的时候,由于手续费问题或者以太网络拥堵问题,会导致你的这笔交易还没有被矿工打包进区块,因此一开始是查不到的,通常需要几十秒甚至更长的时间才能获取到结果。我目前的解决方案是轮询的去刷BlockHash,一开始的时候BlockHash的值为0x00000000000,等到打包成功的时候就不再是0了。这里我使用的是rxjava的方式去轮询刷的,5s刷新一次。 /** * 开启轮询 * 根据txhash查询交易是否被打包进区块 * * @param txHash */ public static void startPolling(String txHash) { //5s刷新一次 disposable = Flowable.interval(0, 5, TimeUnit.SECONDS) .compose(ScheduleCompat.apply()) // .takeUntil(Flowable.timer(120, TimeUnit.SECONDS)) .subscribe(new Consumer<Long>() { @Override public void accept(Long num) throws Exception { Log.i(TAG, "第 : " + num + " 次轮询"); //根据blockHash来判断交易是否被打包 String blockHash = getBlockHash(txHash); boolean isSuccess = Numeric.toBigInt(blockHash).compareTo(BigInteger.valueOf(0)) != 0; if (isSuccess) { getTransactionReceipt(txHash); } } }); } /** * 停止轮询 * * @param disposable */ public static void stopPolling(Disposable disposable) { if (!disposable.isDisposed()) { disposable.dispose(); } } /** * 获取blockhash * @param txHash * @return */ public static String getBlockHash(String txHash) { Web3j web3j = Web3jFactory.build(new HttpService("https://rinkeby.infura.io/v3/xxxx")); try { EthTransaction transaction = web3j.ethGetTransactionByHash(txHash).sendAsync().get(); Transaction result = transaction.getResult(); String blockHash = result.getBlockHash(); Log.i(TAG, "getTransactionResult blockHash : " + blockHash); boolean isSuccess = Numeric.toBigInt(blockHash).compareTo(BigInteger.valueOf(0)) != 0; if (isSuccess) { getTransactionReceipt(txHash); stopPolling(disposable); } return blockHash; } catch (Exception e) { e.printStackTrace(); return ""; } } public static void getTransactionReceipt(String transactionHash) { Web3j web3j = Web3jFactory.build(new HttpService("https://rinkeby.infura.io/v3/xxxx")); try { EthGetTransactionReceipt transactionReceipt = web3j.ethGetTransactionReceipt(transactionHash).sendAsync().get(); TransactionReceipt receipt = transactionReceipt.getTransactionReceipt(); String status = receipt.getStatus(); BigInteger gasUsed = receipt.getGasUsed(); BigInteger blockNumber = receipt.getBlockNumber(); String blockHash = receipt.getBlockHash(); Log.i(TAG, "getTransactionReceipt status : " + status); Log.i(TAG, "getTransactionReceipt gasUsed : " + gasUsed); Log.i(TAG, "getTransactionReceipt blockNumber : " + blockNumber); Log.i(TAG, "getTransactionReceipt blockHash : " + blockHash); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } 正常情况下,几十秒内就可以获取到区块信息了。区块确认数区块确认数=当前区块高度-交易被打包时的区块高度。
2023年03月03日
11 阅读
0 评论
0 点赞
2023-03-03
Etherscan 上传和验证合约源码
之前发布合约的时候都没有注意到这个问题,我也是最近在Etherscan浏览器查看自己合约的时候才发现的这个问题,于是就开始解决一下呗。我看了几篇博客,我认为下面这一篇就讲的很详细。https://www.jianshu.com/p/56082f942bb3细节我就不多说了,我说说其中比较关键的一些地方,以及大家比较容易犯错的地方吧。大家先来看看,没有验证过代码的合约是这样的在Remix编译通过后,点击【Details】获取byteCode信息Details-------> ByteCode----->object注意:这里是编译刚通过的时候就要去获取这个ByteCode,而不是等到Run那里填参数之后。60806040526002805460ff1916601217905534801561001d57600080fd5b50604051610a15380380610a1583398101604090815281516020808401518385015160025460ff16600a0a84026003819055600160a060020a0333166000908152600485529586205590850180519395909491019261007e9285019061009b565b50805161009290600190602084019061009b565b50505050610136565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100dc57805160ff1916838001178555610109565b82800160010185558215610109579182015b828111156101095782518255916020019190600101906100ee565b50610115929150610119565b5090565b61013391905b80821115610115576000815560010161011f565b90565b6108d0806101456000396000f3006080604052600436106100b95763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100be578063095ea7b31461014857806318160ddd1461018057806323b872dd146101a7578063313ce567146101d157806342966c68146101fc57806370a082311461021457806379cc67901461023557806395d89b4114610259578063a9059cbb1461026e578063cae9ca5114610294578063dd62ed3e146102fd575b600080fd5b3480156100ca57600080fd5b506100d3610324565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561010d5781810151838201526020016100f5565b50505050905090810190601f16801561013a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015457600080fd5b5061016c600160a060020a03600435166024356103b2565b604080519115158252519081900360200190f35b34801561018c57600080fd5b506101956103e2565b60408051918252519081900360200190f35b3480156101b357600080fd5b5061016c600160a060020a03600435811690602435166044356103e8565b3480156101dd57600080fd5b506101e661045f565b6040805160ff9092168252519081900360200190f35b34801561020857600080fd5b5061016c600435610468565b34801561022057600080fd5b50610195600160a060020a03600435166104f2565b34801561024157600080fd5b5061016c600160a060020a0360043516602435610504565b34801561026557600080fd5b506100d36105e0565b34801561027a57600080fd5b50610292600160a060020a036004351660243561063a565b005b3480156102a057600080fd5b50604080516020600460443581810135601f810184900484028501840190955284845261016c948235600160a060020a03169460248035953695946064949201919081908401838280828437509497506106499650505050505050565b34801561030957600080fd5b50610195600160a060020a0360043581169060243516610780565b6000805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103aa5780601f1061037f576101008083540402835291602001916103aa565b820191906000526020600020905b81548152906001019060200180831161038d57829003601f168201915b505050505081565b600160a060020a033381166000908152600560209081526040808320938616835292905220819055600192915050565b60035481565b600160a060020a0380841660009081526005602090815260408083203390941683529290529081205482111561041d57600080fd5b600160a060020a038085166000908152600560209081526040808320339094168352929052208054839003905561045584848461079d565b5060019392505050565b60025460ff1681565b600160a060020a03331660009081526004602052604081205482111561048d57600080fd5b600160a060020a03331660008181526004602090815260409182902080548690039055600380548690039055815185815291517fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca59281900390910190a2506001919050565b60046020526000908152604090205481565b600160a060020a03821660009081526004602052604081205482111561052957600080fd5b600160a060020a038084166000908152600560209081526040808320339094168352929052205482111561055c57600080fd5b600160a060020a038084166000818152600460209081526040808320805488900390556005825280832033909516835293815290839020805486900390556003805486900390558251858152925191927fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5929081900390910190a250600192915050565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103aa5780601f1061037f576101008083540402835291602001916103aa565b61064533838361079d565b5050565b60008361065681856103b2565b156107785780600160a060020a0316638f4ffcb1338630876040518563ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018085600160a060020a0316600160a060020a0316815260200184815260200183600160a060020a0316600160a060020a0316815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561070c5781810151838201526020016106f4565b50505050905090810190601f1680156107395780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561075b57600080fd5b505af115801561076f573d6000803e3d6000fd5b50505050600191505b509392505050565b600560209081526000928352604080842090915290825290205481565b6000600160a060020a03831615156107b457600080fd5b600160a060020a0384166000908152600460205260409020548211156107d957600080fd5b600160a060020a038316600090815260046020526040902054828101116107ff57600080fd5b50600160a060020a038083166000818152600460209081526040808320805495891680855282852080548981039091559486905281548801909155815187815291519390950194927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600160a060020a0380841660009081526004602052604080822054928716825290205401811461089e57fe5b505050505600a165627a7a723058202fcf433c82780039ff26c6a0f0f297aee8999f119942eed8a2afb54bb1e35aa30029输入构造参数,部署合约之后得到60806040526002805460ff1916601217905534801561001d57600080fd5b50604051610a15380380610a1583398101604090815281516020808401518385015160025460ff16600a0a84026003819055600160a060020a0333166000908152600485529586205590850180519395909491019261007e9285019061009b565b50805161009290600190602084019061009b565b50505050610136565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100dc57805160ff1916838001178555610109565b82800160010185558215610109579182015b828111156101095782518255916020019190600101906100ee565b50610115929150610119565b5090565b61013391905b80821115610115576000815560010161011f565b90565b6108d0806101456000396000f3006080604052600436106100b95763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100be578063095ea7b31461014857806318160ddd1461018057806323b872dd146101a7578063313ce567146101d157806342966c68146101fc57806370a082311461021457806379cc67901461023557806395d89b4114610259578063a9059cbb1461026e578063cae9ca5114610294578063dd62ed3e146102fd575b600080fd5b3480156100ca57600080fd5b506100d3610324565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561010d5781810151838201526020016100f5565b50505050905090810190601f16801561013a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015457600080fd5b5061016c600160a060020a03600435166024356103b2565b604080519115158252519081900360200190f35b34801561018c57600080fd5b506101956103e2565b60408051918252519081900360200190f35b3480156101b357600080fd5b5061016c600160a060020a03600435811690602435166044356103e8565b3480156101dd57600080fd5b506101e661045f565b6040805160ff9092168252519081900360200190f35b34801561020857600080fd5b5061016c600435610468565b34801561022057600080fd5b50610195600160a060020a03600435166104f2565b34801561024157600080fd5b5061016c600160a060020a0360043516602435610504565b34801561026557600080fd5b506100d36105e0565b34801561027a57600080fd5b50610292600160a060020a036004351660243561063a565b005b3480156102a057600080fd5b50604080516020600460443581810135601f810184900484028501840190955284845261016c948235600160a060020a03169460248035953695946064949201919081908401838280828437509497506106499650505050505050565b34801561030957600080fd5b50610195600160a060020a0360043581169060243516610780565b6000805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103aa5780601f1061037f576101008083540402835291602001916103aa565b820191906000526020600020905b81548152906001019060200180831161038d57829003601f168201915b505050505081565b600160a060020a033381166000908152600560209081526040808320938616835292905220819055600192915050565b60035481565b600160a060020a0380841660009081526005602090815260408083203390941683529290529081205482111561041d57600080fd5b600160a060020a038085166000908152600560209081526040808320339094168352929052208054839003905561045584848461079d565b5060019392505050565b60025460ff1681565b600160a060020a03331660009081526004602052604081205482111561048d57600080fd5b600160a060020a03331660008181526004602090815260409182902080548690039055600380548690039055815185815291517fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca59281900390910190a2506001919050565b60046020526000908152604090205481565b600160a060020a03821660009081526004602052604081205482111561052957600080fd5b600160a060020a038084166000908152600560209081526040808320339094168352929052205482111561055c57600080fd5b600160a060020a038084166000818152600460209081526040808320805488900390556005825280832033909516835293815290839020805486900390556003805486900390558251858152925191927fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5929081900390910190a250600192915050565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103aa5780601f1061037f576101008083540402835291602001916103aa565b61064533838361079d565b5050565b60008361065681856103b2565b156107785780600160a060020a0316638f4ffcb1338630876040518563ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018085600160a060020a0316600160a060020a0316815260200184815260200183600160a060020a0316600160a060020a0316815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561070c5781810151838201526020016106f4565b50505050905090810190601f1680156107395780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561075b57600080fd5b505af115801561076f573d6000803e3d6000fd5b50505050600191505b509392505050565b600560209081526000928352604080842090915290825290205481565b6000600160a060020a03831615156107b457600080fd5b600160a060020a0384166000908152600460205260409020548211156107d957600080fd5b600160a060020a038316600090815260046020526040902054828101116107ff57600080fd5b50600160a060020a038083166000818152600460209081526040808320805495891680855282852080548981039091559486905281548801909155815187815291519390950194927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600160a060020a0380841660009081526004602052604080822054928716825290205401811461089e57fe5b505050505600a165627a7a723058202fcf433c82780039ff26c6a0f0f297aee8999f119942eed8a2afb54bb1e35aa300290000000000000000000000000000000000000000000000000000000000000834000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000074d79546f6b656e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d54000000000000000000000000000000000000000000000000000000000000注意:添加构造参数,部署和鱼成功之后与初始的byteCode比较发现两者就多了后面一段字节数据:0000000000000000000000000000000000000000000000000000000000000834000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000074d79546f6b656e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d54000000000000000000000000000000000000000000000000000000000000这一段字节数据就是我们的构造参数的byteCode我就在这里犯过错,之前一直没找到构造参数的byteCode,大家务必按照步骤来,不然后面通不过。盗图借上面这篇文章的上传源码的图说明几点。第一:上面获取到的构造参数的byteCode复制到最下面的那个框中就行(切记按照步骤,我就踩过坑)第二:编译版本不是指你solidity合约代码中的版本,而是如图所示的这个(这里也踩过坑)验证成功之后的页面大概是这样的到此,恭喜你已经成功的上传和验证通过你的合约源码了!
2023年03月03日
8 阅读
0 评论
0 点赞
2023-03-03
【ETH钱包开发04】web3j转账ERC-20 Token
在上一篇文章中讲解了ETH转账,这一篇讲一下ERC-20 Token转账。【ETH钱包开发03】web3j转账ETHERC-20 Token转账的2种方式1、直接用web3j的API2、java/Android调用合约的transfer方法不管用哪种方式来转账,你都需要先写一个solidity智能合约文件来创建ERC-20 Token,然后部署合约,最后才是通过客户端来调用。web3j API转账ERC-20 Token/** * ERC-20Token交易 * * @param from * @param to * @param value * @param privateKey * @param data 附加信息需要转换成16进制数 * @return * @throws Exception */ public EthSendTransaction transferERC20Token(String from, String to, BigInteger value, String privateKey, String contractAddress) throws Exception { //加载转账所需的凭证,用私钥 Credentials credentials = Credentials.create(privateKey); //获取nonce,交易笔数 BigInteger nonce = getNonce(from); //get gasPrice BigInteger gasPrice = requestCurrentGasPrice(); BigInteger gasLimit = Contract.GAS_LIMIT; //创建RawTransaction交易对象 Function function = new Function( "transfer", Arrays.asList(new Address(to), new Uint256(value)), Arrays.asList(new TypeReference<Type>() { })); String encodedFunction = FunctionEncoder.encode(function); RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, contractAddress, encodedFunction); //签名Transaction,这里要对交易做签名 byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signMessage); //发送交易 EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get(); return ethSendTransaction; } /** * 获取nonce,交易笔数 * * @param from * @return * @throws ExecutionException * @throws InterruptedException */ private BigInteger getNonce(String from) throws ExecutionException, InterruptedException { EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger nonce = transactionCount.getTransactionCount(); Log.i(TAG, "transfer nonce : " + nonce); return nonce; } 注意:erc-20 token转账和eth转账的区别如下:1、erc-20 token创建交易对象用的是这个方法createTransaction2、erc-20 token需要构建Function,它其实对应的就是erc-20 token合约中的那些方法。它的第一个参数就是ERC20中那几个方法的名称,第二个参数的话就是对应合约方法中的参数,第三个参数是和第二个参数对应的,按照我那样就行了。转账的话就是transfer,我们从合约的transfer可以看到第一个参数是收款地址,第二个参数是金额,所以Function这里对应起来就好。 //创建Function对象 Function function = new Function( "transfer", Arrays.asList(new Address(to), new Uint256(value)), Arrays.asList(new TypeReference<Type>() { })); String encodedFunction = FunctionEncoder.encode(function); // 如下为 ERC20 Token 标准接口:----------- // ERC20 Token Standard Interface // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md // ---------------------------------------------------------------------------- contract ERC20 { function name() constant returns (string name) function symbol() constant returns (string symbol) function decimals() constant returns (uint8 decimals) function totalSupply() constant returns (uint totalSupply); function balanceOf(address _owner) constant returns (uint balance); function transfer(address _to, uint _value) returns (bool success); function transferFrom(address _from, address _to, uint _value) returns (bool success); function approve(address _spender, uint _value) returns (bool success); function allowance(address _owner, address _spender) constant returns (uint remaining); event Transfer(address indexed _from, address indexed _to, uint _value); event Approval(address indexed _owner, address indexed _spender, uint _value); } java/Android调用合约的transfer方法这种方法不需要使用web3j封装的方法,而是直接调用solidity合约的方法。步骤1、web3j加载一个已经部署的合约2、验证合约是否加载成功isValid3、如何加载合约成功,则调用合约的transfer方法 /** * ERC-20Token交易(调用solidity合约方式) 推荐使用 * * @param contractAddress 合约地址 * @param to 收款地址 * @param value 转账金额 * @param privateKey 私钥 * @throws ExecutionException * @throws InterruptedException */ public void transferERC20Token(String contractAddress, String to, BigInteger value, String privateKey) throws ExecutionException, InterruptedException { //加载转账所需的凭证,用私钥 Credentials credentials = Credentials.create(privateKey); TokenERC20 contract = TokenERC20.load( contractAddress, web3j, credentials, Contract.GAS_PRICE, Contract.GAS_LIMIT); TransactionReceipt transactionReceipt = contract.transfer(to, value).sendAsync().get(); String transactionHash = transactionReceipt.getTransactionHash(); Log.i(TAG, "transferERC20Token transactionHash : " + transactionHash); } 注意:1、这里的TokenERC20是根据solidity智能合约生成的对应的Java类,用于java/Android和智能合约交互的,如果你对这里不太清楚,不妨看看我之前的一篇文章。以太坊Web3j命令行生成Java版本的智能合约2、如果加载合约失败,可能的一个原因是合约对应的Java类中的BINARY的值不对,这个值是你部署合约成功之后的bytecode,你最好检查对比一下。我发送一笔交易,可以通过这个地址查询https://rinkeby.etherscan.io/tx/0x05bd947e73068badbd9937854169f020980795da8a8182a67e9c2c1888f1874d
2023年03月03日
5 阅读
0 评论
0 点赞
2023-03-03
【ETH钱包开发05】web3j查询ERC-20 Token余额
往期回顾【ETH钱包开发01】创建、导出钱包【ETH钱包开发02】导入钱包【ETH钱包开发03】web3j转账ETH【ETH钱包开发04】web3j转账ERC-20 Token查询eth余额比较简单,这样web3j.ethGetBalance就可以了。但是之前很多朋友在问查询ERC-20Token余额该怎么办?今天就跟大家讲讲常见的几种办法。获取ERC-20Token余额需要2个参数,要查询的地址和ERC-20Token的合约地址。方案一:通过调用合约的方法来查询// 如下为 ERC20 Token 标准接口:----------- // ERC20 Token Standard Interface // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md // ---------------------------------------------------------------------------- contract ERC20 { function name() constant returns (string name) function symbol() constant returns (string symbol) function decimals() constant returns (uint8 decimals) function totalSupply() constant returns (uint totalSupply); function balanceOf(address _owner) constant returns (uint balance); function transfer(address _to, uint _value) returns (bool success); function transferFrom(address _from, address _to, uint _value) returns (bool success); function approve(address _spender, uint _value) returns (bool success); function allowance(address _owner, address _spender) constant returns (uint remaining); event Transfer(address indexed _from, address indexed _to, uint _value); event Approval(address indexed _owner, address indexed _spender, uint _value); } 其实ERC-20相关的很多信息都可以通过上诉的方法来查询。加载合约load 验证合约是否可用isValid 调用合约查询余额的方法balanceOf /** * 查询erc-20 token 余额 * * @param address 要查询的地址 * @param contractAddress erc-20合约地址 * @throws Exception */ public String getERC20Balance(String address, String contractAddress) throws Exception { //加载合约 TokenERC20 contract = TokenERC20.load( contractAddress, web3j, credentials, Contract.GAS_PRICE, Contract.GAS_LIMIT); BigInteger balanceOf = contract.balanceOf(address).sendAsync().get(); String balance = toDecimal(18, balanceOf); Log.i(TAG, "getERC20Balance : " + balanceOf); Log.i(TAG, "getERC20Balance : " + balance); return balance; } public String toDecimal(int decimal, BigInteger integer) { StringBuffer sbf = new StringBuffer("1"); for (int i = 0; i < decimal; i++) { sbf.append("0"); } String balance = new BigDecimal(integer).divide(new BigDecimal(sbf.toString()), 18, BigDecimal.ROUND_DOWN).toPlainString(); return balance; } 注意:1、这里关键在于加载合约是否成功,你需要调用isValid来验证结果,返回true则表示加载成功,则可以继续后续操作,否则就需要检查加载的过程是否出错了。2、decimal 是合约创建时指定的精度方案二:web3j封装的API /** * 获取ERC-20 token指定地址余额 * * @param address 查询地址 * @param contractAddress 合约地址 * @return * @throws ExecutionException * @throws InterruptedException */ public String getERC20Balance(String address, String contractAddress) throws ExecutionException, InterruptedException { Function function = new Function("balanceOf", Arrays.asList(new Address(address)), Arrays.asList(new TypeReference<Address>() { })); String encode = FunctionEncoder.encode(function); Log.i(TAG, "getERC20Balance encode : " + encode); Transaction ethCallTransaction = Transaction.createEthCallTransaction(address, contractAddress, encode); EthCall ethCall = web3j.ethCall(ethCallTransaction, DefaultBlockParameterName.LATEST).sendAsync().get(); String value = ethCall.getResult(); Log.i(TAG, "getERC20Balance balance : " + value); return value; } 这种方案我查了一下,API确实是这样的,网上也有不少人使用,但是返回结果却是0x这样的,我暂时还不能理解,有了解的朋友可以告诉我一下。方案三:根据etherscan API接口来查询https://etherscan.io/apis#tokensetherscan 其实提供了很多API来查询交易、账号等信息,你只需要拼接URL,请求数据,返回json解析就好了。比如,查询ERC20-Token指定账号的余额,可以通过下面这个接口来完成。https://etherscan.io/apishttps://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=0x57d90b64a1a57749b0f932f1a3395792e12e7055&address=0xe04f27eb70e025b78871a2ad7eabe85e61212761&tag=latest&apikey=YourApiKeyToken参数详解module : 固定值account action : 固定值tokenbalance contractaddress : erc-20token的合约地址 address : 要查询的账户的地址 tag : 固定值latest apikey : apikey 是在etherscan调用那些接口都需要用到的一个参数,它是需要你去申请的,注册账号之后就能得到。每个账户最多持有 3 个 token, 请求 API service 服务, 仅需其中一个即可。
2023年03月03日
7 阅读
0 评论
0 点赞
2023-03-03
【ETH钱包开发06】查询某个地址的交易记录
问这个问题的朋友挺多的,包括我自己之前也困惑,因为在web3j没有找到合适的API。还好,经过一番研究,找到了一些可行的办法,仅供大家参考,希望大家多多探讨。1、etherscan api提供了查询历史交易记录的接口,返回json。2、用web3j filter过滤器监听交易,将所有的交易数据存储到本地数据库中,需要查询的时候直接从本地数据库中查询。方案一:etherscan API查询http://api.etherscan.io/api?module=account&action=txlist&address=0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae&startblock=0&endblock=99999999&sort=asc&apikey=YourApiKeyToken只能返回最近的1000条交易信息,不过应该够用了吧。或者https://api.etherscan.io/api?module=account&action=txlist&address=0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae&startblock=0&endblock=99999999&page=1&offset=10&sort=asc&apikey=YourApiKeyToken这种方式返回指定个数的交易信息注意:1、module、action、module、sort都是固定值。2、startblock和endblock是指查询2个区块区间的所有指定地址交易,通常startblock为0,endblock设置为"latest"即可。3、apikey需要申请,apikey 是在etherscan调用那些接口都需要用到的一个参数,它是需要你去申请的,注册账号之后就能得到。每个账户最多持有 3 个 token, 请求 API service 服务, 仅需其中一个即可。返回结果方案二:Filter过滤器这种方式需要后端来实现,我在android端试过,会有一些异常。大家可以参考官方文档https://docs.web3j.io/getting_started.html#gradle
2023年03月03日
43 阅读
0 评论
0 点赞
2023-03-03
【ETH智能合约】--05 Solidity 事件Event详解
什么是Evnet事件事件是以太坊虚拟机(EVM)日志基础设施提供的一个接口,事件可以用来做操作记录,存储为日志。如果监听了某事件,当事件发生时,会进行回调。当定义的事件触发时,我们可以将事件存储到EVM的交易的日志中(日志是区块链上的一种特殊数据结构)。日志是与合约地址关联的,并存储在区块链中。注意:区块链上的交易往往会有日志记录,日志代表着智能合约所触发的事件。 不能直接访问日志和事件数据,即使是创建日志的合约。 在Solidity中,使用event 关键字来定义一个事件。event EventName(address bidder, uint amount); 事件在合约中可以被继承。触发一个事件使用emit(之前的版本里并不需要使用emit)emit EventName(msg.sender, msg.value); 触发事件的调用function testEvent() public { // 触发一个事件 emit EventName(msg.sender, msg.value); } 监听事件在Web3.js与智能合约交互中,可以通过Event来实现一些交互功能。接下来一起来看看在之前web3的代码中,加入Event,并触发事件在之前的合约中添加了一个Event事件pragma solidity ^0.4.21; contract InfoContract { string fName; uint age; event Instructor( string name, uint age ); function setInfo(string _fName, uint _age) public { fName = _fName; age = _age; } function getInfo() public constant returns (string, uint) { return (fName, age); } } 然后修改setInfo函数,触发Instructor事件function setInfo(string _fName, uint _age) public { fName = _fName; age = _age; emit Instructor(_fName, _age); } 当调用setInfo函数,函数时触发Instructor事件。现在通过Web3.js监听事件,刷新UI。<script> if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); } else { // set the provider you want from Web3.providers web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); } web3.eth.defaultAccount = web3.eth.accounts[0]; var infoContract = web3.eth.contract(ABI INFO); var info = infoContract.at('CONTRACT ADDRESS'); info.getInfo(function(error, result){ if(!error) { $("#info").html(result[0]+' ('+result[1]+' years old)'); console.log(result); } else console.error(error); }); $("#button").click(function() { info.setInfo($("#name").val(), $("#age").val()); }); </script> 现在,我们改一下代码,通过监听事件获取信息,所以不需要info.getInfo()来获取信息了var instructorEvent = info.Instructor(); instructorEvent.watch(function(error, result) { if (!error) { $("#info").html(result.args.name + ' (' + result.args.age + ' years old)'); } else { console.log(error); } }); 这样修改以后,就可以刷新UI了。参考:https://coursetro.com/posts/code/100/Solidity-Events-Tutorial---Using-Web3.js-to-Listen-for-Smart-Contract-Eventshttps://github.com/ethereum/wiki/wiki/JavaScript-API#contract-events
2023年03月03日
12 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之 miner/worker.go (上)
Go-ethereum 源码解析之 miner/worker.go (上)Source Code// Copyright 2015 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. package miner import ( "bytes" "errors" "math/big" "sync" "sync/atomic" "time" mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" ) const ( // resultQueueSize is the size of channel listening to sealing result. resultQueueSize = 10 // txChanSize is the size of channel listening to NewTxsEvent. // The number is referenced from the size of tx pool. txChanSize = 4096 // chainHeadChanSize is the size of channel listening to ChainHeadEvent. chainHeadChanSize = 10 // chainSideChanSize is the size of channel listening to ChainSideEvent. chainSideChanSize = 10 // resubmitAdjustChanSize is the size of resubmitting interval adjustment channel. resubmitAdjustChanSize = 10 // miningLogAtDepth is the number of confirmations before logging successful mining. miningLogAtDepth = 7 // minRecommitInterval is the minimal time interval to recreate the mining block with // any newly arrived transactions. minRecommitInterval = 1 * time.Second // maxRecommitInterval is the maximum time interval to recreate the mining block with // any newly arrived transactions. maxRecommitInterval = 15 * time.Second // intervalAdjustRatio is the impact a single interval adjustment has on sealing work // resubmitting interval. intervalAdjustRatio = 0.1 // intervalAdjustBias is applied during the new resubmit interval calculation in favor of // increasing upper limit or decreasing lower limit so that the limit can be reachable. intervalAdjustBias = 200 * 1000.0 * 1000.0 // staleThreshold is the maximum depth of the acceptable stale block. staleThreshold = 7 ) // environment is the worker's current environment and holds all of the current state information. type environment struct { signer types.Signer state *state.StateDB // apply state changes here ancestors mapset.Set // ancestor set (used for checking uncle parent validity) family mapset.Set // family set (used for checking uncle invalidity) uncles mapset.Set // uncle set tcount int // tx count in cycle gasPool *core.GasPool // available gas used to pack transactions header *types.Header txs []*types.Transaction receipts []*types.Receipt } // task contains all information for consensus engine sealing and result submitting. type task struct { receipts []*types.Receipt state *state.StateDB block *types.Block createdAt time.Time } const ( commitInterruptNone int32 = iota commitInterruptNewHead commitInterruptResubmit ) // newWorkReq represents a request for new sealing work submitting with relative interrupt notifier. type newWorkReq struct { interrupt *int32 noempty bool timestamp int64 } // intervalAdjust represents a resubmitting interval adjustment. type intervalAdjust struct { ratio float64 inc bool } // worker is the main object which takes care of submitting new work to consensus engine // and gathering the sealing result. type worker struct { config *params.ChainConfig engine consensus.Engine eth Backend chain *core.BlockChain gasFloor uint64 gasCeil uint64 // Subscriptions mux *event.TypeMux txsCh chan core.NewTxsEvent txsSub event.Subscription chainHeadCh chan core.ChainHeadEvent chainHeadSub event.Subscription chainSideCh chan core.ChainSideEvent chainSideSub event.Subscription // Channels newWorkCh chan *newWorkReq taskCh chan *task resultCh chan *types.Block startCh chan struct exitCh chan struct resubmitIntervalCh chan time.Duration resubmitAdjustCh chan *intervalAdjust current *environment // An environment for current running cycle. possibleUncles map[common.Hash]*types.Block // A set of side blocks as the possible uncle blocks. unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations. mu sync.RWMutex // The lock used to protect the coinbase and extra fields coinbase common.Address extra []byte pendingMu sync.RWMutex pendingTasks map[common.Hash]*task snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot snapshotBlock *types.Block snapshotState *state.StateDB // atomic status counters running int32 // The indicator whether the consensus engine is running or not. newTxs int32 // New arrival transaction count since last sealing work submitting. // Test hooks newTaskHook func(*task) // Method to call upon receiving a new sealing task. skipSealHook func(*task) bool // Method to decide whether skipping the sealing. fullTaskHook func() // Method to call before pushing the full sealing task. resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval. } func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, recommit time.Duration, gasFloor, gasCeil uint64) *worker { worker := &worker{ config: config, engine: engine, eth: eth, mux: mux, chain: eth.BlockChain(), gasFloor: gasFloor, gasCeil: gasCeil, possibleUncles: make(map[common.Hash]*types.Block), unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth), pendingTasks: make(map[common.Hash]*task), txsCh: make(chan core.NewTxsEvent, txChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), newWorkCh: make(chan *newWorkReq), taskCh: make(chan *task), resultCh: make(chan *types.Block, resultQueueSize), exitCh: make(chan struct), startCh: make(chan struct, 1), resubmitIntervalCh: make(chan time.Duration), resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize), } // Subscribe NewTxsEvent for tx pool worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) // Subscribe events for blockchain worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) // Sanitize recommit interval if the user-specified one is too short. if recommit < minRecommitInterval { log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval) recommit = minRecommitInterval } go worker.mainLoop() go worker.newWorkLoop(recommit) go worker.resultLoop() go worker.taskLoop() // Submit first work to initialize pending state. worker.startCh <- struct return worker } // setEtherbase sets the etherbase used to initialize the block coinbase field. func (w *worker) setEtherbase(addr common.Address) { w.mu.Lock() defer w.mu.Unlock() w.coinbase = addr } // setExtra sets the content used to initialize the block extra field. func (w *worker) setExtra(extra []byte) { w.mu.Lock() defer w.mu.Unlock() w.extra = extra } // setRecommitInterval updates the interval for miner sealing work recommitting. func (w *worker) setRecommitInterval(interval time.Duration) { w.resubmitIntervalCh <- interval } // pending returns the pending state and corresponding block. func (w *worker) pending() (*types.Block, *state.StateDB) { // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() if w.snapshotState == nil { return nil, nil } return w.snapshotBlock, w.snapshotState.Copy() } // pendingBlock returns pending block. func (w *worker) pendingBlock() *types.Block { // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() return w.snapshotBlock } // start sets the running status as 1 and triggers new work submitting. func (w *worker) start() { atomic.StoreInt32(&w.running, 1) w.startCh <- struct } // stop sets the running status as 0. func (w *worker) stop() { atomic.StoreInt32(&w.running, 0) } // isRunning returns an indicator whether worker is running or not. func (w *worker) isRunning() bool { return atomic.LoadInt32(&w.running) == 1 } // close terminates all background threads maintained by the worker. // Note the worker does not support being closed multiple times. func (w *worker) close() { close(w.exitCh) } // newWorkLoop is a standalone goroutine to submit new mining work upon received events. func (w *worker) newWorkLoop(recommit time.Duration) { var ( interrupt *int32 minRecommit = recommit // minimal resubmit interval specified by user. timestamp int64 // timestamp for each round of mining. ) timer := time.NewTimer(0) <-timer.C // discard the initial tick // commit aborts in-flight transaction execution with given signal and resubmits a new one. commit := func(noempty bool, s int32) { if interrupt != nil { atomic.StoreInt32(interrupt, s) } interrupt = new(int32) w.newWorkCh <- &newWorkReq timer.Reset(recommit) atomic.StoreInt32(&w.newTxs, 0) } // recalcRecommit recalculates the resubmitting interval upon feedback. recalcRecommit := func(target float64, inc bool) { var ( prev = float64(recommit.Nanoseconds()) next float64 ) if inc { next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target+intervalAdjustBias) // Recap if interval is larger than the maximum time interval if next > float64(maxRecommitInterval.Nanoseconds()) { next = float64(maxRecommitInterval.Nanoseconds()) } } else { next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target-intervalAdjustBias) // Recap if interval is less than the user specified minimum if next < float64(minRecommit.Nanoseconds()) { next = float64(minRecommit.Nanoseconds()) } } recommit = time.Duration(int64(next)) } // clearPending cleans the stale pending tasks. clearPending := func(number uint64) { w.pendingMu.Lock() for h, t := range w.pendingTasks { if t.block.NumberU64()+staleThreshold <= number { delete(w.pendingTasks, h) } } w.pendingMu.Unlock() } for { select { case <-w.startCh: clearPending(w.chain.CurrentBlock().NumberU64()) timestamp = time.Now().Unix() commit(false, commitInterruptNewHead) case head := <-w.chainHeadCh: clearPending(head.Block.NumberU64()) timestamp = time.Now().Unix() commit(false, commitInterruptNewHead) case <-timer.C: // If mining is running resubmit a new work cycle periodically to pull in // higher priced transactions. Disable this overhead for pending blocks. if w.isRunning() && (w.config.Clique == nil || w.config.Clique.Period > 0) { // Short circuit if no new transaction arrives. if atomic.LoadInt32(&w.newTxs) == 0 { timer.Reset(recommit) continue } commit(true, commitInterruptResubmit) } case interval := <-w.resubmitIntervalCh: // Adjust resubmit interval explicitly by user. if interval < minRecommitInterval { log.Warn("Sanitizing miner recommit interval", "provided", interval, "updated", minRecommitInterval) interval = minRecommitInterval } log.Info("Miner recommit interval update", "from", minRecommit, "to", interval) minRecommit, recommit = interval, interval if w.resubmitHook != nil { w.resubmitHook(minRecommit, recommit) } case adjust := <-w.resubmitAdjustCh: // Adjust resubmit interval by feedback. if adjust.inc { before := recommit recalcRecommit(float64(recommit.Nanoseconds())/adjust.ratio, true) log.Trace("Increase miner recommit interval", "from", before, "to", recommit) } else { before := recommit recalcRecommit(float64(minRecommit.Nanoseconds()), false) log.Trace("Decrease miner recommit interval", "from", before, "to", recommit) } if w.resubmitHook != nil { w.resubmitHook(minRecommit, recommit) } case <-w.exitCh: return } } } // mainLoop is a standalone goroutine to regenerate the sealing task based on the received event. func (w *worker) mainLoop() { defer w.txsSub.Unsubscribe() defer w.chainHeadSub.Unsubscribe() defer w.chainSideSub.Unsubscribe() for { select { case req := <-w.newWorkCh: w.commitNewWork(req.interrupt, req.noempty, req.timestamp) case ev := <-w.chainSideCh: if _, exist := w.possibleUncles[ev.Block.Hash()]; exist { continue } // Add side block to possible uncle block set. w.possibleUncles[ev.Block.Hash()] = ev.Block // If our mining block contains less than 2 uncle blocks, // add the new uncle block if valid and regenerate a mining block. if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2 { start := time.Now() if err := w.commitUncle(w.current, ev.Block.Header()); err == nil { var uncles []*types.Header w.current.uncles.Each(func(item interface) bool { hash, ok := item.(common.Hash) if !ok { return false } uncle, exist := w.possibleUncles[hash] if !exist { return false } uncles = append(uncles, uncle.Header()) return false }) w.commit(uncles, nil, true, start) } } case ev := <-w.txsCh: // Apply transactions to the pending state if we're not mining. // // Note all transactions received may not be continuous with transactions // already included in the current mining block. These transactions will // be automatically eliminated. if !w.isRunning() && w.current != nil { w.mu.RLock() coinbase := w.coinbase w.mu.RUnlock() txs := make(map[common.Address]types.Transactions) for _, tx := range ev.Txs { acc, _ := types.Sender(w.current.signer, tx) txs[acc] = append(txs[acc], tx) } txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs) w.commitTransactions(txset, coinbase, nil) w.updateSnapshot() } else { // If we're mining, but nothing is being processed, wake on new transactions if w.config.Clique != nil && w.config.Clique.Period == 0 { w.commitNewWork(nil, false, time.Now().Unix()) } } atomic.AddInt32(&w.newTxs, int32(len(ev.Txs))) // System stopped case <-w.exitCh: return case <-w.txsSub.Err(): return case <-w.chainHeadSub.Err(): return case <-w.chainSideSub.Err(): return } } } // taskLoop is a standalone goroutine to fetch sealing task from the generator and // push them to consensus engine. func (w *worker) taskLoop() { var ( stopCh chan struct prev common.Hash ) // interrupt aborts the in-flight sealing task. interrupt := func() { if stopCh != nil { close(stopCh) stopCh = nil } } for { select { case task := <-w.taskCh: if w.newTaskHook != nil { w.newTaskHook(task) } // Reject duplicate sealing work due to resubmitting. sealHash := w.engine.SealHash(task.block.Header()) if sealHash == prev { continue } // Interrupt previous sealing operation interrupt() stopCh, prev = make(chan struct), sealHash if w.skipSealHook != nil && w.skipSealHook(task) { continue } w.pendingMu.Lock() w.pendingTasks[w.engine.SealHash(task.block.Header())] = task w.pendingMu.Unlock() if err := w.engine.Seal(w.chain, task.block, w.resultCh, stopCh); err != nil { log.Warn("Block sealing failed", "err", err) } case <-w.exitCh: interrupt() return } } } // resultLoop is a standalone goroutine to handle sealing result submitting // and flush relative data to the database. func (w *worker) resultLoop() { for { select { case block := <-w.resultCh: // Short circuit when receiving empty result. if block == nil { continue } // Short circuit when receiving duplicate result caused by resubmitting. if w.chain.HasBlock(block.Hash(), block.NumberU64()) { continue } var ( sealhash = w.engine.SealHash(block.Header()) hash = block.Hash() ) w.pendingMu.RLock() task, exist := w.pendingTasks[sealhash] w.pendingMu.RUnlock() if !exist { log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash) continue } // Different block could share same sealhash, deep copy here to prevent write-write conflict. var ( receipts = make([]*types.Receipt, len(task.receipts)) logs []*types.Log ) for i, receipt := range task.receipts { receipts[i] = new(types.Receipt) *receipts[i] = *receipt // Update the block hash in all logs since it is now available and not when the // receipt/log of individual transactions were created. for _, log := range receipt.Logs { log.BlockHash = hash } logs = append(logs, receipt.Logs...) } // Commit block and state to database. stat, err := w.chain.WriteBlockWithState(block, receipts, task.state) if err != nil { log.Error("Failed writing block to chain", "err", err) continue } log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) // Broadcast the block and announce chain insertion event w.mux.Post(core.NewMinedBlockEvent) var events []interface switch stat { case core.CanonStatTy: events = append(events, core.ChainEvent) events = append(events, core.ChainHeadEvent) case core.SideStatTy: events = append(events, core.ChainSideEvent) } w.chain.PostChainEvents(events, logs) // Insert the block into the set of pending ones to resultLoop for confirmations w.unconfirmed.Insert(block.NumberU64(), block.Hash()) case <-w.exitCh: return } } } // makeCurrent creates a new environment for the current cycle. func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { state, err := w.chain.StateAt(parent.Root()) if err != nil { return err } env := &environment{ signer: types.NewEIP155Signer(w.config.ChainID), state: state, ancestors: mapset.NewSet(), family: mapset.NewSet(), uncles: mapset.NewSet(), header: header, } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { for _, uncle := range ancestor.Uncles() { env.family.Add(uncle.Hash()) } env.family.Add(ancestor.Hash()) env.ancestors.Add(ancestor.Hash()) } // Keep track of transactions which return errors so they can be removed env.tcount = 0 w.current = env return nil } // commitUncle adds the given block to uncle block set, returns error if failed to add. func (w *worker) commitUncle(env *environment, uncle *types.Header) error { hash := uncle.Hash() if env.uncles.Contains(hash) { return errors.New("uncle not unique") } if env.header.ParentHash == uncle.ParentHash { return errors.New("uncle is sibling") } if !env.ancestors.Contains(uncle.ParentHash) { return errors.New("uncle's parent unknown") } if env.family.Contains(hash) { return errors.New("uncle already included") } env.uncles.Add(uncle.Hash()) return nil } // updateSnapshot updates pending snapshot block and state. // Note this function assumes the current variable is thread safe. func (w *worker) updateSnapshot() { w.snapshotMu.Lock() defer w.snapshotMu.Unlock() var uncles []*types.Header w.current.uncles.Each(func(item interface) bool { hash, ok := item.(common.Hash) if !ok { return false } uncle, exist := w.possibleUncles[hash] if !exist { return false } uncles = append(uncles, uncle.Header()) return false }) w.snapshotBlock = types.NewBlock( w.current.header, w.current.txs, uncles, w.current.receipts, ) w.snapshotState = w.current.state.Copy() } func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { snap := w.current.state.Snapshot() receipt, _, err := core.ApplyTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vm.Config) if err != nil { w.current.state.RevertToSnapshot(snap) return nil, err } w.current.txs = append(w.current.txs, tx) w.current.receipts = append(w.current.receipts, receipt) return receipt.Logs, nil } func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { // Short circuit if current is nil if w.current == nil { return true } if w.current.gasPool == nil { w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit) } var coalescedLogs []*types.Log for { // In the following three cases, we will interrupt the execution of the transaction. // (1) new head block event arrival, the interrupt signal is 1 // (2) worker start or restart, the interrupt signal is 1 // (3) worker recreate the mining block with any newly arrived transactions, the interrupt signal is 2. // For the first two cases, the semi-finished work will be discarded. // For the third case, the semi-finished work will be submitted to the consensus engine. if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { // Notify resubmit loop to increase resubmitting interval due to too frequent commits. if atomic.LoadInt32(interrupt) == commitInterruptResubmit { ratio := float64(w.current.header.GasLimit-w.current.gasPool.Gas()) / float64(w.current.header.GasLimit) if ratio < 0.1 { ratio = 0.1 } w.resubmitAdjustCh <- &intervalAdjust{ ratio: ratio, inc: true, } } return atomic.LoadInt32(interrupt) == commitInterruptNewHead } // If we don't have enough gas for any further transactions then we're done if w.current.gasPool.Gas() < params.TxGas { log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas) break } // Retrieve the next transaction and abort if all done tx := txs.Peek() if tx == nil { break } // Error may be ignored here. The error has already been checked // during transaction acceptance is the transaction pool. // // We use the eip155 signer regardless of the current hf. from, _ := types.Sender(w.current.signer, tx) // Check whether the tx is replay protected. If we're not in the EIP155 hf // phase, start ignoring the sender until we do. if tx.Protected() && !w.config.IsEIP155(w.current.header.Number) { log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.config.EIP155Block) txs.Pop() continue } // Start executing the transaction w.current.state.Prepare(tx.Hash(), common.Hash, w.current.tcount) logs, err := w.commitTransaction(tx, coinbase) switch err { case core.ErrGasLimitReached: // Pop the current out-of-gas transaction without shifting in the next from the account log.Trace("Gas limit exceeded for current block", "sender", from) txs.Pop() case core.ErrNonceTooLow: // New head notification data race between the transaction pool and miner, shift log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) txs.Shift() case core.ErrNonceTooHigh: // Reorg notification data race between the transaction pool and miner, skip account = log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) txs.Pop() case nil: // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) w.current.tcount++ txs.Shift() default: // Strange error, discard the transaction and get the next in line (note, the // nonce-too-high clause will prevent us from executing in vain). log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) txs.Shift() } } if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are mining. The reason is that // when we are mining, the worker will regenerate a mining block every 3 seconds. // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing. // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined // logs by filling in the block hash when the block was mined by the local miner. This can // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. cpy := make([]*types.Log, len(coalescedLogs)) for i, l := range coalescedLogs { cpy[i] = new(types.Log) *cpy[i] = *l } go w.mux.Post(core.PendingLogsEvent) } // Notify resubmit loop to decrease resubmitting interval if current interval is larger // than the user-specified one. if interrupt != nil { w.resubmitAdjustCh <- &intervalAdjust } return false } // commitNewWork generates several new sealing tasks based on the parent block. func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) { w.mu.RLock() defer w.mu.RUnlock() tstart := time.Now() parent := w.chain.CurrentBlock() if parent.Time().Cmp(new(big.Int).SetInt64(timestamp)) >= 0 { timestamp = parent.Time().Int64() + 1 } // this will ensure we're not going off too far in the future if now := time.Now().Unix(); timestamp > now+1 { wait := time.Duration(timestamp-now) * time.Second log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) time.Sleep(wait) } num := parent.Number() header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), GasLimit: core.CalcGasLimit(parent, w.gasFloor, w.gasCeil), Extra: w.extra, Time: big.NewInt(timestamp), } // Only set the coinbase if our consensus engine is running (avoid spurious block rewards) if w.isRunning() { if w.coinbase == (common.Address) { log.Error("Refusing to mine without etherbase") return } header.Coinbase = w.coinbase } if err := w.engine.Prepare(w.chain, header); err != nil { log.Error("Failed to prepare header for mining", "err", err) return } // If we are care about TheDAO hard-fork check whether to override the extra-data or not if daoBlock := w.config.DAOForkBlock; daoBlock != nil { // Check whether the block is among the fork extra-override range limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 { // Depending whether we support or oppose the fork, override differently if w.config.DAOForkSupport { header.Extra = common.CopyBytes(params.DAOForkBlockExtra) } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { header.Extra = []byte // If miner opposes, don't let it use the reserved extra-data } } } // Could potentially happen if starting to mine in an odd state. err := w.makeCurrent(parent, header) if err != nil { log.Error("Failed to create mining context", "err", err) return } // Create the current work task and check any fork transitions needed env := w.current if w.config.DAOForkSupport && w.config.DAOForkBlock != nil && w.config.DAOForkBlock.Cmp(header.Number) == 0 { misc.ApplyDAOHardFork(env.state) } // Accumulate the uncles for the current block for hash, uncle := range w.possibleUncles { if uncle.NumberU64()+staleThreshold <= header.Number.Uint64() { delete(w.possibleUncles, hash) } } uncles := make([]*types.Header, 0, 2) for hash, uncle := range w.possibleUncles { if len(uncles) == 2 { break } if err := w.commitUncle(env, uncle.Header()); err != nil { log.Trace("Possible uncle rejected", "hash", hash, "reason", err) } else { log.Debug("Committing new uncle to block", "hash", hash) uncles = append(uncles, uncle.Header()) } } if !noempty { // Create an empty block based on temporary copied state for sealing in advance without waiting block // execution finished. w.commit(uncles, nil, false, tstart) } // Fill the block with all available pending transactions. pending, err := w.eth.TxPool().Pending() if err != nil { log.Error("Failed to fetch pending transactions", "err", err) return } // Short circuit if there is no available pending transactions if len(pending) == 0 { w.updateSnapshot() return } // Split the pending transactions into locals and remotes localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending for _, account := range w.eth.TxPool().Locals() { if txs := remoteTxs[account]; len(txs) > 0 { delete(remoteTxs, account) localTxs[account] = txs } } if len(localTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs) if w.commitTransactions(txs, w.coinbase, interrupt) { return } } if len(remoteTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs) if w.commitTransactions(txs, w.coinbase, interrupt) { return } } w.commit(uncles, w.fullTaskHook, true, tstart) } // commit runs any post-transaction state modifications, assembles the final block // and commits new work if consensus engine is running. func (w *worker) commit(uncles []*types.Header, interval func(), update bool, start time.Time) error { // Deep copy receipts here to avoid interaction between different tasks. receipts := make([]*types.Receipt, len(w.current.receipts)) for i, l := range w.current.receipts { receipts[i] = new(types.Receipt) *receipts[i] = *l } s := w.current.state.Copy() block, err := w.engine.Finalize(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts) if err != nil { return err } if w.isRunning() { if interval != nil { interval() } select { case w.taskCh <- &task: w.unconfirmed.Shift(block.NumberU64() - 1) feesWei := new(big.Int) for i, tx := range block.Transactions() { feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice())) } feesEth := new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether))) log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start))) case <-w.exitCh: log.Info("Worker has exited") } } if update { w.updateSnapshot() } return nil } Appendix A. 协程批注本附录中用于描述本文件中启动了哪些协程,各自又是通过哪些通道进行消息交互的。同时,对于会被使用到的外部协程也进行了简单的描述。1. 命名协程在对象 miner.worker 的构造函数 newWorker() 中启动新的独立协程运行方法 worker.mainLoop(),不妨将此协程称作命名协程 worker.mainLoop()。 在对象 miner.worker 的构造函数 newWorker() 中启动新的独立协程运行方法 worker.newWorkLoop(recommit),不妨将此协程称作命名协程 worker.newWorkLoop()。 在对象 miner.worker 的构造函数 newWorker() 中启动新的独立协程运行方法 worker.resultLoop(),不妨将此协程称作命名协程 worker.resultLoop()。 在对象 miner.worker 的构造函数 newWorker() 中启动新的独立协程运行方法 worker.taskLoop(),不妨将此协程称作命名协程 worker.taskLoop()。 消息在上述四个命名协程中的流转方向:命名协程 worker.newWorkLoop() 基于接收到的消息向命名协程 worker.mainLoop() 提交事件 miner.newWorkReq,事件的提交最终是在命名协程 worker.newWorkLoop() 的内置函数 commit() 中完成。 命名协程 worker.mainLoop() 基于接收到的消息向命名协程 worker.taskLoop() 提交任务 miner.task,任务的提交最终是在命名协程 worker.mainLoop() 调用的方法 worker.commitNewWork() 和方法 worker.commit() 中完成。 命名协程 worker.taskLoop() 基于接收到的消息向命名协程 worker.resultLoop() 提交已签名区块 types.Block,已签名区块的提交最终是在共识引擎的签名方法 clique.Seal() 的匿名协程中完成。 各命名协程接收消息和发送消息的具体描述:命名协程 worker.newWorkLoop() 从通道 worker.startCh 接收驱动 worker 的开始事件 struct,从通道 worker.chainHeadCh 接收事件 core.ChainHeadEvent,从通道 timer.C 接收事件 time.Time,从通道 worker.resubmitIntervalCh 接收事件 time.Duration,从通道 worker.resubmitAdjustCh 接收事件 intervalAdjust。命名协程 worker.newWorkLoop() 向通道 worker.newWorkCh 发送事件 miner.newWorkReq。 命名协程 worker.mainLoop() 从通道 worker.newWorkCh 接收事件 miner.newWorkReq,从通道 worker.chainSideCh 接收事件 core.ChainSideEvent,从通道 worker.txsCh 接收事件 core.NewTxsEvent。命名协程 worker.mainLoop() 向通道 worker.taskCh 发送事件 miner.task。 命名协程 worker.taskLoop() 从通道 worker.taskCh 接收事件 miner.task。命名协程 worker.taskLoop() 通过共识引擎的签名方法 clique.Seal() 最终向通道 worker.resultCh 发送消息 types.Block。 命名协程 worker.resultLoop() 从通道 worker.resultCh 接收事件 types.Block。命名协程 worker.resultLoop() 通过方法 TypeMux.Post() 将最终的签名区块广播给网络中其它节点,通过 BlockChain.PostChainEvents() 将签名区块及其对应的事件向本地节点的事件订阅者通过 JSON-RPC 的方式发送事件。 2. 匿名协程在共识引擎的签名方法 Cilque.Seal() 中启动了匿名协程,用于将已签名区块发送给通道 worker.resultCh。 在方法 commitTransactions() 中启动一个独立的匿名协程,将所有得到正常处理的交易产生的日志集合通过方法 TypeMux.Post() 发送给订阅者。方法 commitTransactions() 由命名协程 worker.mainLoop() 调用。 Appendix B. 日志信息这些日志记录了关键的流程,同时记录了可能的出错原因。1. 需要重点关注的日志log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas) log.Trace("Gas limit exceeded for current block", "sender", from) log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) log.Error("Refusing to mine without etherbase") log.Error("Failed to prepare header for mining", "err", err) log.Debug("Committing new uncle to block", "hash", hash) log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start))) 2. 其它日志log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval) log.Warn("Sanitizing miner recommit interval", "provided", interval, "updated", minRecommitInterval) log.Info("Miner recommit interval update", "from", minRecommit, "to", interval) log.Trace("Increase miner recommit interval", "from", before, "to", recommit) log.Trace("Decrease miner recommit interval", "from", before, "to", recommit) log.Warn("Block sealing failed", "err", err) log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash) log.Error("Failed writing block to chain", "err", err) log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.config.EIP155Block) log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) log.Error("Failed to create mining context", "err", err) log.Trace("Possible uncle rejected", "hash", hash, "reason", err) log.Error("Failed to fetch pending transactions", "err", err) log.Info("Worker has exited") Appendix C. 总体批注1. const定义了挖矿流程相关的一些常数。特别需要注意两个以共识协议相关的常量 miningLogAtDepth 和 staleThreshold,目前都是 7。可以简单地这样理解,对于给定区块,该区块在经过 miningLogAtDepth 个区块之后被整个链确认。2. type environment struct定义了数据结构 environment,用于描述当前挖矿所需的环境。3. type task struct定义了数据结构 task,用于描述发送给共识引擎进行签名的待签名区块,以及从共识引擎接收的已签名区块。4. const定义了中断相关的一些枚举值,用于描述中断信号。5. type newWorkReq struct定义了数据结构 newWorkReq,用于描述如何开始一个新任务。任务所需的具体信息包含在当前环境 environment 中。6. type intervalAdjust struct定义了数据结构 intervalAdjust,描述重新提交间隔调整所需的参数。同时,需要注意,另外一些参数是通过 timer.Time 定时器提供的。7. type worker struct定义了数据结构 worker。对象 worker 是挖矿的主要实现,启动了多个协程来执行独立的逻辑流程:构建挖矿的当前环境 接收交易 接收已签名区块 提交交易 构建当前正在挖的区块及任务 组装区块头 使用共识引擎设定区块头中的共识相关字段、对整个区块进行最终的签名 将经共识引擎已签名的区块进行广播 构造函数 newWorker() 用于根据给定参数构建 worker。 方法 setEtherbase() 设置用于初始化区块 coinbase 字段的 etherbase。 方法 setExtra() 设置用于初始化区块额外字段的内容。 方法 setRecommitInterval() 更新矿工签名工作重新提交的间隔。 方法 pending() 返回待处理的状态和相应的区块。 方法 pendingBlock() 返回待处理的区块。 方法 start() 采用原子操作将 running 字段置为 1,并触发新工作的提交。 方法 stop() 采用原子操作将 running 字段置为 0。 方法 isRunning() 返回 worker 是否正在运行的指示符。 方法 close() 终止由 worker 维护的所有后台线程。注意 worker 不支持被关闭多次,这是由 Go 语言不允许多次关闭同一个通道决定的。 方法 newWorkLoop() 是一个独立的协程,基于接收到的事件提交新的挖矿工作。不妨将此协程称作命名协程 worker.newWorkLoop()。 方法 mainLoop() 是一个独立的协程,用于根据接收到的事件重新生成签名任务。不妨将此协程称作命名协程 worker.mainLoop()。 方法 taskLoop() 是一个独立的协程,用于从生成器中获取待签名任务,并将它们提交给共识引擎。不妨将此协程称作命名协程 worker.taskLoop()。 方法 resultLoop() 是一个独立的协程,用于处理签名区块的提交和广播,以及更新相关数据到数据库。不妨将此协程称作命名协程 worker.resultLoop()。 方法 makeCurrent() 为当前周期创建新的环境 environment。 方法 commitUncle() 将给定的区块添加至叔区块集合中,如果添加失败则返回错误。 方法 updateSnapshot() 更新待处理区块和状态的快照。注意,此函数确保当前变量是线程安全的。 方法 commitTransactions() 提交交易列表 txs,并附上交易的发起者地址。根据整个交易列表 txs 是否都被有效提交,返回 true 或 false。 方法 commitNewWork() 基于父区块生成几个新的签名任务。 方法 commit() 运行任何交易的后续状态修改,组装最终区块,并在共识引擎运行时提交新工作。 Referencehttps://github.com/ethereum/go-ethereum/blob/master/miner/worker.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
31 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之重要的数据结构和算法
Go-ethereum 源码解析之重要的数据结构和算法1. 数据结构(1) 哈希32 个字节,256 位。由算法 Keccak-256 计算。文件:go-ethereum/common/types.goconst ( HashLength = 32 ) type Hash [HashLength]byte (2) 地址20 个字节,160 位。由算法 RIPEMD-160 计算。文件:go-ethereum/common/types.goconst ( AddressLength = 20 ) type Address [AddressLength]byte (3) 区块头文件:go-ethereum/core/types/block.gotype Header struct { ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Difficulty *big.Int `json:"difficulty" gencodec:"required"` Number *big.Int `json:"number" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` Time *big.Int `json:"timestamp" gencodec:"required"` Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash" gencodec:"required"` Nonce BlockNonce `json:"nonce" gencodec:"required"` } (4) 交易文件:go-ethereum/core/types/transaction.gotype Transaction struct { data txdata // caches hash atomic.Value size atomic.Value from atomic.Value } type txdata struct { AccountNonce uint64 `json:"nonce" gencodec:"required"` Price *big.Int `json:"gasPrice" gencodec:"required"` GasLimit uint64 `json:"gas" gencodec:"required"` Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation Amount *big.Int `json:"value" gencodec:"required"` Payload []byte `json:"input" gencodec:"required"` // Signature values V *big.Int `json:"v" gencodec:"required"` R *big.Int `json:"r" gencodec:"required"` S *big.Int `json:"s" gencodec:"required"` // This is only used when marshaling to JSON. Hash *common.Hash `json:"hash" rlp:"-"` } (5) 交易回执文件:go-ethereum/core/types/receipt.gotype Receipt struct { // Consensus fields PostState []byte `json:"root"` Status uint64 `json:"status"` CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` // Implementation fields (don't reorder!) TxHash common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress common.Address `json:"contractAddress"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` } (6) 日志项文件:go-ethereum/core/types/log.gotype Log struct { // Consensus fields: // address of the contract that generated the event Address common.Address `json:"address" gencodec:"required"` // list of topics provided by the contract. Topics []common.Hash `json:"topics" gencodec:"required"` // supplied by the contract, usually ABI-encoded Data []byte `json:"data" gencodec:"required"` // Derived fields. These fields are filled in by the node // but not secured by consensus. // block in which the transaction was included BlockNumber uint64 `json:"blockNumber"` // hash of the transaction TxHash common.Hash `json:"transactionHash" gencodec:"required"` // index of the transaction in the block TxIndex uint `json:"transactionIndex" gencodec:"required"` // hash of the block in which the transaction was included BlockHash common.Hash `json:"blockHash"` // index of the log in the receipt Index uint `json:"logIndex" gencodec:"required"` // The Removed field is true if this log was reverted due to a chain reorganisation. // You must pay attention to this field if you receive logs through a filter query. Removed bool `json:"removed"` } (7) 区块文件:go-ethereum/core/types/block.gotype Block struct { header *Header uncles []*Header transactions Transactions // caches hash atomic.Value size atomic.Value // Td is used by package core to store the total difficulty // of the chain up to and including the block. td *big.Int // These fields are used by package eth to track // inter-peer block relay. ReceivedAt time.Time ReceivedFrom interface } 2. 算法(1) keccak256计算 Ethereum-SHA-3(Keccak-256)散列值。也被用来计算 Solidity 中的函数签名(仅使用散列值的前 4 个字节,8 个十六进制数)。(2) sha3keccak256 的别名。(3) sha256计算 SHA-256 散列值。(4) ripemd160计算 RIPEMD-160 散列值。这是非对称加密?公钥和私钥?(5) secp256k1签名算法,生成 65 个字节的签名信息,或者 R, S, V?签名方法:secp256k1.Sign() 恢复签名方法:secp256k1.RecoverPubkey() 【备注】在比特币中,采用非对称加密算法 secp256k1 根据给定的密码求出公钥和私钥,然后对公钥采用 SHA3 家族的散列算法 ripemd160 对公钥进行二次散列,散列为 20 字节,并将散列值作为账户地址。 而在以太坊中,采用非对称加密算法 secp256k1 根据给定的密码求出公钥和私钥,然后对公钥采用 SHA3 家族的散列算法 keccak256 对公钥进行二次散列,散列为 32 字节,将将散列值的前 20 个字节作为账户地址。 3. 重要的概念(1) 区块头哈希区块头哈希是指采用散列算法 keccak256 对区块头中所有数据计算出的散列值。(2) 区块头签名哈希区块头签名哈希是指采用散列算法 keccak256 对区块头中除了额外数据中的最后 65 个字节之外所有数据计算出的散列值。(3) 区块哈希区块哈希,即区块头哈希。(4) 区块签名哈希区块签名哈希即区块头签名哈希。(5) 账户地址 & 以太坊账户地址 & 以太坊账户以太坊账户地址即 common.Address,包含 20 个字节。(6) 智能合约地址 & 以太坊智能合约地址以太坊智能合约地址即 common.Address,包含 20 个字节。在以太坊中账户地址和智能合约地址基本相同,但也有一些显著的差别:账户地址由人操控,或者说由以太坊的外部操控。 智能合约地址由账户地址操控。 (7) 签名者签名者即以太坊账户地址。(8) 签名区块 & 待确定区块签名区块是本地节点最新挖出来的区块,存入本地节点的待确定区块列表,需要等待网络中其它节点的验证。签名区块中,区块头、交易列表、交易回执列表都已经组装完成,并且区块头中也已经包含了签名。Referencehttps://github.com/ethereum/go-ethereum/blob/master/miner/worker.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
18 阅读
0 评论
0 点赞
1
2
3