$ bitcoin-cli help importaddress
importaddress "address" ( "label" rescan p2sh )

添加一个脚本(16 进制)或地址用于查看,就好像它在你的钱包里但不能用于花费。

参数:
1. "script"(字符串,必备)16 进制编码的脚本(或地址)
2. "label" (字符串,可选,默认为 "")一个可选的标签
3. rescan  (布尔型,可选,默认为 true)重新扫描钱包交易
4. p2sh    (布尔型,可选,默认为 true)添加脚本的 P2SH 版本

注:如果重新扫描为 true,这个调用要花费数分钟来完成。
如果你有完整的公钥,你应该调用 importpubkey 代替这个。

例子:

导入一个脚本并重新扫描
> bitcoin-cli importaddress "myscript"

导入地址到钱包账户 "testing" 中,并关闭再扫描。
> bitcoin-cli importaddress "myscript" "testing" false

作为一个 JSON-RPC 调用
> curl --user myusername:mypassword --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "importaddress", "params": ["myscript", "testing", false] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/

源码剖析

importaddress 对应的函数在文件 rpcserver.h 中被引用。

extern UniValue importaddress(const UniValue& params, bool fHelp);

实现在文件 wallet/rpcdump.cpp 中。

UniValue importaddress(const UniValue& params, bool fHelp)
{
    if (!EnsureWalletIsAvailable(fHelp)) // 1. 确保钱包可用
        return NullUniValue;
    
    if (fHelp || params.size() < 1 || params.size() > 4)
        throw runtime_error(
            "importaddress \"address\" ( \"label\" rescan p2sh )\n"
            "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n"
            "\nArguments:\n"
            "1. \"script\"           (string, required) The hex-encoded script (or address)\n"
            "2. \"label\"            (string, optional, default=\"\") An optional label\n"
            "3. rescan               (boolean, optional, default=true) Rescan the wallet for transactions\n"
            "4. p2sh                 (boolean, optional, default=false) Add the P2SH version of the script as well\n"
            "\nNote: This call can take minutes to complete if rescan is true.\n"
            "If you have the full public key, you should call importpublickey instead of this.\n"
            "\nExamples:\n"
            "\nImport a script with rescan\n"
            + HelpExampleCli("importaddress", "\"myscript\"") +
            "\nImport using a label without rescan\n"
            + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") +
            "\nAs a JSON-RPC call\n"
            + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false")
        ); // 2. 帮助内容


    string strLabel = "";
    if (params.size() > 1)
        strLabel = params[1].get_str();

    // Whether to perform rescan after import
    bool fRescan = true; // 3. 在导入后是否执行重新扫描
    if (params.size() > 2)
        fRescan = params[2].get_bool();

    if (fRescan && fPruneMode)
        throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");

    // Whether to import a p2sh version, too
    bool fP2SH = false; // 4. 是否也导入一个 p2sh 版本
    if (params.size() > 3)
        fP2SH = params[3].get_bool();

    LOCK2(cs_main, pwalletMain->cs_wallet);

    CBitcoinAddress address(params[0].get_str());
    if (address.IsValid()) {
        if (fP2SH)
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
        ImportAddress(address, strLabel); // 导入地址及其关联账户
    } else if (IsHex(params[0].get_str())) {
        std::vector<unsigned char> data(ParseHex(params[0].get_str()));
        ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); // 导入脚本
    } else {
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
    }

    if (fRescan)
    {
        pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); // 重新扫描钱包交易
        pwalletMain->ReacceptWalletTransactions(); // 把交易放入内存池
    }

    return NullUniValue;
}

1. 确保钱包可用

参考比特币 RPC 命令「fundrawtransaction」1. 确保钱包可用

2. 帮助内容

参考比特币 RPC 命令「getbestblockhash」1. 帮助内容

4. 是否也导入一个 p2sh 版本

导入地址函数 ImportAddress(address, strLabel) 实现在文件 wallet/rpcdump.cpp 中。

void ImportAddress(const CBitcoinAddress& address, const string& strLabel)
{
    CScript script = GetScriptForDestination(address.Get()); // 通过脚本索引获取脚本
    ImportScript(script, strLabel, false); // 导入脚本
    // add to address book or update label // 添加到地址簿或更新账户
    if (address.IsValid()) // 若该地址有效
        pwalletMain->SetAddressBook(address.Get(), strLabel, "receive"); // 添加地址及关联账户、用途到地址簿
}

导入脚本函数 ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH) 实现在文件 wallet/rpcdump.cpp 中。

void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript) // 导入脚本
{
    if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) // P2SH 类型 且 是自己的脚本
        throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");

    pwalletMain->MarkDirty(); // 标记钱包已改变

    if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script)) // 若 watch-only 集合中没有指定脚本,则添加该脚本到 watch-only 脚本集合
        throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");

    if (isRedeemScript) { // 若为赎回脚本
        if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script))
            throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
        ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel); // 导入地址及关联账户
    }
}

Watch-only 脚本对象定义在文件 keystore.h 的基础密钥存储类 CBasicKeyStore 中。

typedef std::set<CScript> WatchOnlySet; // watch-only 脚本集合

/** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore // 基础密钥存储,以 address->secret 映射维持私钥
{
protected:
    KeyMap mapKeys; // 私钥和索引的映射列表
    WatchKeyMap mapWatchKeys; // 公钥和索引的映射列表,用于 watch-only
    ScriptMap mapScripts; // 脚本索引映射列表
    WatchOnlySet setWatchOnly; // watch-only 脚本集合
    ...
};

重新接受钱包交易函数 pwalletMain->ReacceptWalletTransactions() 定义在文件 wallet/wallet.cpp 中。

void CWallet::ReacceptWalletTransactions()
{
    // If transactions aren't being broadcasted, don't let them into local mempool either
    if (!fBroadcastTransactions) // 如果交易未被广播,也不让它们进入本地交易内存池
        return;
    LOCK2(cs_main, cs_wallet); // 钱包上锁
    std::map<int64_t, CWalletTx*> mapSorted; // 位置与钱包交易映射列表

    // Sort pending wallet transactions based on their initial wallet insertion order // 基于它们初始的钱包交易顺序来排序挂起的钱包交易
    BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) // 遍历钱包交易映射列表
    {
        const uint256& wtxid = item.first; // 获取交易索引
        CWalletTx& wtx = item.second; // 获取钱包交易
        assert(wtx.GetHash() == wtxid); // 验证交易与其索引是否一致

        int nDepth = wtx.GetDepthInMainChain(); // 获取交易在主链中的深度

        if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { // 该交易不能是 coinbase 且 该交易深度为 0(表示该交易还未被接受)且该交易未被抛弃
            mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); // 加入临时映射列表
        }
    }

    // Try to add wallet transactions to memory pool // 尝试添加钱包交易到内存池
    BOOST_FOREACH(PAIRTYPE(const int64_t, CWalletTx*)& item, mapSorted) // 遍历该列表
    {
        CWalletTx& wtx = *(item.second); // 获取钱包交易

        LOCK(mempool.cs); // 内存池上锁
        wtx.AcceptToMemoryPool(false); // 把交易放入内存池
    }
}
...
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
{
    CValidationState state;
    return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, fRejectAbsurdFee); // 添加交易到内存池
}

参考链接