需求分析可以支持多条链，只要链支持Solidity智能合约即可可以支持多个代币，需要符合ERC20标准的代币由去中心化的中继节点采用投票方式来完成跨链请求的确认每个中继节点需要质押保证金，每成功一笔跨链交易可获取奖励引入惩罚机制，中继节点每次作恶都会被罚没，保证金低于一定值时被封禁，重新充入保证金可解封引入黑名单管理机制，符合监管需求，禁止名单内账户使用跨链桥功能引入中继节点准入机制，维持中继节点集群稳定，保障跨链桥的稳定工作跨链交易规则跨链有最低金额限制（比如100USDT）每笔收取总金额的一定比例作为手续费（比如万分之五）一定比例的手续费平分给参与投票的中继节点（比如70%）剩下比例的手续费发给平台（比如30%）用户需手动去领取跨链资产，也可以在指定一个帮忙自动提取的中继节点（需额外付费）中继节点规则中继节点列表由平台进行管理维护，需保持稳定（避免在短时间内大量增加或减少）保证金由中继节点自行维护，存入自由，取出需要限制（比如每隔24小时可取一次）中继节点数量需保持一定合适的数量，至少也得3个，太多中继节点也不好（浪费机器资源，也拖慢投票速度）投票罚没规则中继节点需保证金大于指定门槛的才可以参与投票（比如10万），低于的处于封禁状态中继节点的投票权重都一样，和保证金大小无关每次跨链请求需要大于2/3的中继节点（所有的包括封禁的）投票才算通过投票时需要附带中继节点的签名（合约可以验证签名来核查投票结果并允许用户提取对应资产）每次作恶罚没一定比例的保证金（比如5万），罚没的保证金转给平台地址如果某个节点持续作恶，会不断损失保证金，平台也可以将其从中继节点列表中移除跨链通道要进行跨链交易，需要在两条链之间创建一个通道通道是有方向的，一般情况要添加双向的：A链->B链和B链->A链通道需指定两条链关联的代币地址相同的两条链可添加多对代币地址参与角色普通用户-web前端：发起跨链请求、领取跨链资产、查看交易记录（历史的和进行中的）中继节点-后台服务：跨链交易投票、帮用户自动领取跨链资产中继节点-web前端：查看管理保证金、查看收益、查看投票记录平台管理-web前端：查看管理中继节点、查看保证金、查看收益列表、查看修改黑名单、查看交易记录数据分析与监控：交易汇总分析、中继节点收益分析、资金流动分析、跨链桥异常预警合约设计核心流程：用户在A链存入（冻结）资产、中继节点投票（签名）确认、使用超2/3的签名在B链取出（解冻）资产引入一个廉价的链来存储投票过程，而不是中继节点自行共识，投票过程更安全，也可追溯，提高系统的可用性、安全性用来投票的链这里称为桥链（bridge chain），它还可用来存储一些其它数据或运行一些服务使用签名技术减少合约调用次数，简化设计与节省交易gas费为了节省交易gas费，所有投票在桥链上完成，其它功能也尽量在桥链上实现投票就是签名，用户投票则是将签名写入到桥链合约中跨链通道定义：通道id=keccak256(来源链id,来源链代币地址,目标链id,目标链代币地址)跨链交易定义：交易id=keccak256(通道id,时间戳,接收者,金额,手续费)跨链交易记为KR，投票就是对KR进行签名，记为：KS，有效投票需满足数据无篡改和签名可验证架构桥核心合约（资产）：跨链资金出入口、中继成员列表、黑名单列表桥治理合约（投票）：签名存储与验证、保证金管理、惩罚机制、手续费分发桥链部署：治理合约A链部署：资产合约B链部署：资产合约C链部署：资产合约桥核心合约接口-添加中继节点接口-移除中继节点接口-将账户地址加入黑名单接口-将账户地址从黑名单删除接口-添加跨链通道接口-移除跨链通道接口-跨链转入资产（用户资产充足且未在黑名单内）接口-跨链取出资产（需通过2/3的中继节点签名验证）事件-中继节点变更、黑名单变更、资产转入、资产转出、资产解冻、交易取消查询-中继节点、黑名单、跨链USDT额度（到不同链上的额度）变量-跨链USDT额度列表、中继节点列表、黑名单列表桥治理合约接口-增加保证金接口-减少保证金接口-上传签名（验证签名，作恶罚没）事件-保证金变更、签名提交、投票结果、罚没查询-保证金、签名、罚没次数、罚没金额变量-保证金余额列表、签名列表、跨链交易列表、惩罚记录表合约执行以以太坊和波场为例，以稳定币USDT为例初始化两条链的桥核心合约设置相同的10个中继节点地址和黑名单列表在桥链的桥治理合约中为10个中继节点地址冲入20万保证金平台往两条链的桥核心合约中存入10万USDT作为流动资金Ethereum >>> Tron 跨链用户调用Ethereum链资产合约的跨链转入接口检查用户不在黑名单内，黑名单用户交易直接失败将用户的1万个USDT转到资产合约地址名下更新变量：到Tron链的跨链USDT额度减少1万发出事件：跨链转入（起始链ID + 目标链ID + 金额 + 目标链代币合约地址）中继节点调用桥链上的桥治理合约上传签名接口接收用户的跨链请求事件，可以等待N个区块确认（避免区块分叉导致交易失效）对此跨链事件进行签名并提交到投票合约中（如果不认可则可以忽略）在签名满7个时检查投票结果，如果有超2/3的有效票则进行以下操作：对此次跨链请求作恶的中继节点执行罚没分发跨链交易手续费发出事件：投票完成（跨链请求交易哈希 + 可提取代币地址 + 可提取金额 + 7个签名）中继节点调用Tron链桥核心合约的跨链转出接口检查合约持有的USDT是否足够检查7个签名是否有效，且签名地址在中继节点列表中给用户指定的接收地址转9995个USDT（从桥核心合约地址名下）更新变量：Ethereum链的跨链USDT额度增加1万发出事件：跨链取出（跨链请求交易哈希 + 代币地址 + 金额）状态变更总结Ethereum链的资产合约:持有的USDT数量：10万 -> 11万桥链的投票合约：签名列表：+ 跨链请求的7个有效签名Tron链的资产合约：持有的USDT数量：10万 -> 9万用户在Ethereum上的USDT减少1万，在Tron链上获得9995个7个中继节点在桥链上获得3.5/7个USDT平台获得1.5个USDTTron >>> Ethereum 跨链此操作和Ethereum >>> Tron跨链操作流程一样跨链手续费同样在桥治理合约中分发一开始只有两条链上的USDT很少，后期会随着交易量增长用户兑换时可检查目标链上是否有充足的币来决定是否发起交易如果用户一时提取不到资产，待桥核心合约有足够的资金再重试跨链桥特点同一笔跨链交易需按顺序执行：A链转入、投票、B链转出需要2/3的中继节点投票，确保每一笔交易都是有效的不同跨链交易可异步执行，桥链的TPS高，速度快使用相对廉价的链合约来处理投票过程，安全性高，透明度高惩罚机制，提高相同的稳定性多链多币支持，拓展性强平台只是审查中继节点列表，跨链交易由中继节点共识投票确认，去中心化程度高
与以太坊的账户模型不同，比特币是没有余额概念的，使用的是UTXO模型。所以在比特币网络中，存储比特币的是交易输出，准确点说就是未使用过的交易输出。UTXOUTXO是Unspent Transaction Outputs的缩写（Transaction可简写为TX），即未使用过的交易输出。比特币节点会追踪UTXO集合，从而确定哪些代币未被花费，以及哪些人可以花费它们。最初的比特币是通过铸币交易创造的，它是用来奖励矿工的一种特殊交易，因为是凭空创造的，所以它没有输入的。新的UTXO是通过花费现有UTXO创建的，UTXO包含了接收地址和金额。每个比特币交易都由输入和输出组成。输入花费已有UTXO，输出则创建新的UTXO，UTXO只要被用一次，就不存在“未花费”了，以后就不会再被引用，这样就解决双花（双重支付）问题。当我们要使用一个UTXO时，需要对UTXO进行解锁（一般是用私钥对UTXO进行签名），虽然UTXO是公开的，但是如果没有私钥去解锁，则不能花费UTXO。交易结构交易是比特币的核心数据结构，包括区块在内的数据结构都是在为交易服务。交易主要由未花费的输入和输出组成，输出总金额不能大于输入的总金额，多余的部分为交易矿工费有三种类型交易铸币交易（Generation TX），有额外的Coinbase字段，矿工可以随意填写一些信息，因为是挖矿所得，所以这种交易类型没有输入，输出数量就是矿工所得，地址是矿工的地址。普通交易（Pubkey Hash TX），转账交易，一个或多个输入，一个或两个输出，第一个输出称为支付，第二个输出称为找零脚本交易（Script Hash TX），一般为多签脚本，为普通交易升级版本，可以生成灵活的锁定脚本，然后使用特定的解锁脚本去花费比特币字段表数据项大小(Byte)数据类型描述nVersion4int32_t交易版本vin不定CTxIn列表交易输入vout不定CTxOut列表交易输出nLockTime4uint32_t交易锁定时间交易输入CTxIn数据项大小(Byte)数据类型描述prevout不定COutPoint上一个交易的输出，由交易哈希和索引值确定scriptSig不定char[]解锁脚本nSequence4uint32_t序列号，可用于相对时间锁定交易输出CTxOut数据项大小(Byte)数据类型描述nValue8int64_t交易输出金额，单位为SatoshisscriptPubKey不定char[]锁定脚本，定义花费须满足的条件创币交易CoinbaseTransaction每一个区块内包含的第一条交易为CoinbaseTransaction，它作为对挖出该区块的矿工的比特币奖励交易。没有交易输入交易哈希为0输出的索引值固定，为0xffffffffBIP34规定增加一个4字节的height的字段，这个参数是必须的源码参考代码路径：https://github.com/bitcoin/bitcoin/blob/master/src/primitives/transaction.hCOutPut/** An outpoint - a combination of a transaction hash and an index n into its vout * ** 一个交易哈希值与输出下标的集合 */ class COutPoint { public: uint256 hash; //交易哈西 uint32_t n; //对应序列号 COutPoint(): n((uint32_t) -1) { } COutPoint(const uint256& hashIn, uint32_t nIn): hash(hashIn), n(nIn) { } ADD_SERIALIZE_METHODS; //用来序列化数据结构，方便存储和传输 template <typename Stream, typename Operation> inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(hash); READWRITE(n); } void SetNull() { hash.SetNull(); n = (uint32_t) -1; } bool IsNull() const { return (hash.IsNull() && n == (uint32_t) -1); } //小于号<重载函数 friend bool operator<(const COutPoint& a, const COutPoint& b) { int cmp = a.hash.Compare(b.hash); return cmp < 0 || (cmp == 0 && a.n < b.n); } //==重载函数 friend bool operator==(const COutPoint& a, const COutPoint& b) { return (a.hash == b.hash && a.n == b.n); } //!=重载函数 friend bool operator!=(const COutPoint& a, const COutPoint& b) { return !(a == b); } std::string ToString() const; };CTxIn/** An input of a transaction. It contains the location of the previous * transaction's output that it claims and a signature that matches the * output's public key. * **交易的输入，包括当前输入所对应上一笔交易的输出位置， *并且还包括上一笔输出所需要的签名脚本 */ class CTxIn { public: COutPoint prevout; //上一笔交易输出位置 CScript scriptSig; //解锁脚本 uint32_t nSequence; /**序列号，可用于交易的锁定 nSequence字段的设计初心是想让交易能在在内存中修改，可惜后面从未运用过 对于具有nLocktime或CHECKLOCKTIMEVERIFY的交易， nSequence值必须设置为小于2^32，以使时间锁定器有效。通常设置为2^32-1 由于BIP-68的激活，新的共识规则适用于任何包含nSequence值小于2^31的输入的交易（bit 1<<31 is not set）。 以编程方式，这意味着如果没有设置最高有效（bit 1<<31），它是一个表示“相对锁定时间”的标志。 否则（bit 1<<31set），nSequence值被保留用于其他用途， 例如启用CHECKLOCKTIMEVERIFY，nLocktime，Opt-In-Replace-By-Fee以及其他未来的新产品。 一笔输入交易，当输入脚本中的nSequence值小于2^31时，就是相对时间锁定的输入交易。 这种交易只有到了相对锁定时间后才生效。例如， 具有30个区块的nSequence相对时间锁的一个输入的交易 只有在从输入中引用的UTXO开始的时间起至少有30个块时才有效。 由于nSequence是每个输入字段，因此交易可能包含任何数量的时间锁定输入， 所有这些都必须具有足够的时间以使交易有效。 */ CScriptWitness scriptWitness; //! Only serialized through CTransaction /* Setting nSequence to this value for every input in a transaction * disables nLockTime. * * 规则1:如果一笔交易中所有的SEQUENCE_FINAL都被赋值了相应的nSequence，那么nLockTime就会被禁用 */ static const uint32_t SEQUENCE_FINAL = 0xffffffff; /* Below flags apply in the context of BIP 68*/ /* If this flag set, CTxIn::nSequence is NOT interpreted as a * relative lock-time. * * 规则2:如果设置了该值，nSequence不被用于相对时间锁定。规则1失效 */ static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31); /* If CTxIn::nSequence encodes a relative lock-time and this flag * is set, the relative lock-time has units of 512 seconds, * otherwise it specifies blocks with a granularity of 1. * * 规则3：如果规则1有效并且设置了此变量，那么相对锁定时间单位为512秒，否则锁定时间就为1个区块 */ static const uint32_t SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22); /* If CTxIn::nSequence encodes a relative lock-time, this mask is * applied to extract that lock-time from the sequence field. * * 规则4：如果nSequence用于相对时间锁，即规则1有效，那么这个变量就用来从nSequence计算对应的锁定时间 */ static const uint32_t SEQUENCE_LOCKTIME_MASK = 0x0000ffff; /* In order to use the same number of bits to encode roughly the * same wall-clock duration, and because blocks are naturally * limited to occur every 600s on average, the minimum granularity * for time-based relative lock-time is fixed at 512 seconds. * Converting from CTxIn::nSequence to seconds is performed by * multiplying by 512 = 2^9, or equivalently shifting up by * 9 bits. * * 相对时间锁粒度 * 为了使用相同的位数来粗略地编码相同的挂钟时间， * 因为区块的产生限制于每600s产生一个， * 相对时间锁定的最小单位为512是，512 = 2^9 * 所以相对时间锁定的时间转化为相当于当前值左移9位 */ static const int SEQUENCE_LOCKTIME_GRANULARITY = 9; CTxIn() { nSequence = SEQUENCE_FINAL; } explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL); CTxIn(uint256 hashPrevTx, uint32_t nOut, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL); ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(prevout); READWRITE(scriptSig); READWRITE(nSequence); } friend bool operator==(const CTxIn& a, const CTxIn& b) { return (a.prevout == b.prevout && a.scriptSig == b.scriptSig && a.nSequence == b.nSequence); } friend bool operator!=(const CTxIn& a, const CTxIn& b) { return !(a == b); } std::string ToString() const; };CTxOut/** An output of a transaction. It contains the public key that the next input * must be able to sign with to claim it. * **交易输出，包含输出金额和锁定脚本 */ class CTxOut { public: CAmount nValue; //输出金额 CScript scriptPubKey; //锁定脚本 CTxOut() { SetNull(); } CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn); ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(nValue); READWRITE(scriptPubKey); } void SetNull() { nValue = -1; scriptPubKey.clear(); } bool IsNull() const { return (nValue == -1); } friend bool operator==(const CTxOut& a, const CTxOut& b) { return (a.nValue == b.nValue && a.scriptPubKey == b.scriptPubKey); } friend bool operator!=(const CTxOut& a, const CTxOut& b) { return !(a == b); } std::string ToString() const; };CTransaction/** The basic transaction that is broadcasted on the network and contained in * blocks. A transaction can contain multiple inputs and outputs. * * ** 基本的交易，就是那些在网络中广播并被最终打包到区块中的数据结构。 * 一个交易可以包含多个交易输入和输出 */ class CTransaction { public: // Default transaction version. static const int32_t CURRENT_VERSION=2; //默认交易版本 // Changing the default transaction version requires a two step process: first // adapting relay policy by bumping MAX_STANDARD_VERSION, and then later date // bumping the default CURRENT_VERSION at which point both CURRENT_VERSION and // MAX_STANDARD_VERSION will be equal. /** 更改默认交易版本需要两个步骤： * 1.首先通过碰撞MAX_STANDARD_VERSION来调整中继策略， * 2.然后在稍后的日期碰撞默认的CURRENT_VERSION * * 最终MAX_STANDARD_VERSION和CURRENT_VERSION会一致 */ static const int32_t MAX_STANDARD_VERSION=2; // The local variables are made const to prevent unintended modification // without updating the cached hash value. However, CTransaction is not // actually immutable; deserialization and assignment are implemented, // and bypass the constness. This is safe, as they update the entire // strcture, including the hash. /** 下面这些变量都被定义为常量类型，从而避免无意识的修改了交易而没有更新缓存的hash值； * 然而CTransaction不是可变的 * 反序列化和分配被执行的时候会绕过常量 * 这才是安全的，因为更新整个结构包括哈希值 */ const std::vector<CTxIn> vin; //交易输入 const std::vector<CTxOut> vout; //交易输出 const int32_t nVersion; //版本 const uint32_t nLockTime; //锁定时间 private: /** Memory only. */ const uint256 hash; uint256 ComputeHash() const; public: /** Construct a CTransaction that qualifies as IsNull() */ CTransaction(); /** Convert a CMutableTransaction into a CTransaction. */ /**可变交易转换为交易*/ CTransaction(const CMutableTransaction &tx); CTransaction(CMutableTransaction &&tx); template <typename Stream> inline void Serialize(Stream& s) const { SerializeTransaction(*this, s); } /** This deserializing constructor is provided instead of an Unserialize method. * Unserialize is not possible, since it would require overwriting const fields. * ** 提供此反序列化构造函数而不是Unserialize方法。 * 反序列化是不可能的，因为它需要覆盖const字段 */ template <typename Stream> CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {} bool IsNull() const { return vin.empty() && vout.empty(); } const uint256& GetHash() const { return hash; } // Compute a hash that includes both transaction and witness data uint256 GetWitnessHash() const; //计算包含交易和witness数据的散列 // Return sum of txouts. CAmount GetValueOut() const; //返回交易出书金额总和 // GetValueIn() is a method on CCoinsViewCache, because // inputs must be known to compute value in. /** * Get the total transaction size in bytes, including witness data. * "Total Size" defined in BIP141 and BIP144. * @return Total transaction size in bytes */ unsigned int GetTotalSize() const; // 返回交易大小 bool IsCoinBase() const //判断是否是创币交易 { return (vin.size() == 1 && vin[0].prevout.IsNull()); } friend bool operator==(const CTransaction& a, const CTransaction& b) { return a.hash == b.hash; } friend bool operator!=(const CTransaction& a, const CTransaction& b) { return a.hash != b.hash; } std::string ToString() const; bool HasWitness() const { for (size_t i = 0; i < vin.size(); i++) { if (!vin[i].scriptWitness.IsNull()) { return true; } } return false; } };