# DYDX V4 chain
# 逻辑流
### 1. 用户发起`placeOrder` order
- 用户通过客户端创建并签名一个`placeOrder` order,指定order细节如买卖资产、价格和数量。
- 签名的order被发送到`dydxprotocol/v4-chain`网络。
### 2. order在网络中的传播
- order被网络中的节点接收,包括可能的区块提议者(验证者)。
- order经过初步的验证,如签名和格式的检查。
- [AnteHandler代码](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/app/ante.go)
### 3. order预处理和验证(AnteHandler)
- 接收节点执行`AnteHandler`,进行诸如签名验证、账户余额和序列号检查等预处理。
- 在AnteHandler维护了orderbook,以及order匹配,将匹配的order或者需要取消的order放入operation列表,等待下一个区块处理。
- 通过预处理的order加入到节点的mempool。
### 4. 区块的创建和提议
- **选定提议者**:根据共识机制,从验证者中选定一个validator作为区块的提议者。
- **构建区块**:
- 提议者从mempool中选择order,包括`placeOrder`。
- 执行order(在提议者节点上):在区块被最终确定并广播之前,提议者节点可能会先在本地执行这些order。对于placeOrder,这意味着在CLOB模块中执行相关逻辑,比如更新订单簿。这通常在模块的keeper中进行。
- 提议者可能还会计算包括trade执行结果的状态更改,并将这些信息包含在区块中。
- **广播区块**:新创建的区块被广播给其他验证者节点。
### 5. 验证者执行预提交阶段
在基于拜占庭容错(BFT)共识机制的区块链网络中,验证者(validator)执行的逻辑和区块提议者(proposer)**执行的逻辑基本是一致**的。这种设计确保了整个网络的一致性和可靠性。
当验证者接收到一个区块提议时,他们收到的数据包括:
1. 区块头(Block Header):包含区块的元数据,如版本号、前一个区块的哈希值、时间戳、区块高度等。还可能包括共识相关的数据,比如提议者的签名、区块的哈希值等。
2. order列表(Transactions):区块中包含的所有order。这些order是由网络中的各个参与者提交的,等待被打包到区块中。order通常包括发起者的签名、order的具体内容(如交易金额、币对等)。
3. 区块的证明(Proof of Block):区块提议可能包括与区块验证相关的证明,例如在某些实现的Merkle树的根哈希值。
4. 附加数据(Additional Data):包括trade执行的结果、状态的变化、或者任何与该区块相关的其他信息。
#### 流程:
- 其他验证者接收新区块,执行初步验证,包括检查区块头和order和trade的合法性。
- 验证者在本地执行区块中的order,包括`placeOrder`,并更新本地trade状态。
- 如果验证结果与区块提议一致,验证者发出预提交投票。
### 6. 区块确认和共识达成
- 一旦超过2/3的验证者对区块发出预提交票,区块被视为有效,并最终确认。
### 7. 应用层角度中的订单簿的最终更新
在placeOrder order的处理流程中,"CLOB模块处理和订单簿更新"放在最后的原因在于它们是在trade被区块链网络确认后才确定执行的。这个顺序确保了order处理的正确性和区块链数据的一致性。
**确保一致性**:这个逻辑顺序是为了保证所有的网络参与者对区块链的状态有一个一致的视图。只有在trade通过网络共识后,状态更新才被认为是最终的。
**防止错误和欺诈**:延迟**执行完成**的概念直到确认防止了潜在的双重支出、trade回滚或其他欺诈行为。
### 结论
从用户发起`placeOrder` order到整个网络共识的完整路径。在这个过程中,区块的创建和提议是一个关键步骤,涉及到从mempool中选择order,创建新区块,并通过共识机制验证和确认这个区块。这确保了trade的正确性和区块链数据的一致性,是区块链技术中维护去中心化、透明性和安全性的关键环节。
# 底层细节
## [AnteHandle](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/ante/clob.go#L41)
1. AnteHandle 方法是 AnteDecorator 的核心方法。
2. 它在 CheckTx 期间处理交易,并根据消息类型进行不同的处理。
3. 如果交易不包含 MsgPlaceOrder 或 MsgCancelOrder,它将不执行任何操作,直接调用下一个 AnteHandler。
4. 如果交易包含多个消息,其中一个是 MsgPlaceOrder 或 MsgCancelOrder,它将返回错误。
5. 对于包含单个 clob 消息的交易,它会根据消息类型执行相应的订单处理逻辑,包括下单和取消订单。
```
func (cd ClobDecorator) AnteHandle(
ctx sdk.Context, // 上下文对象
tx sdk.Tx, // 交易对象
simulate bool, // 是否模拟交易
next sdk.AnteHandler, // 下一个 AnteHandler
) (sdk.Context, error) {
// 步骤 1: 检查是否需要处理
if lib.IsDeliverTxMode(ctx) || simulate {
return next(ctx, tx, simulate) // 如果处于 'DeliverTx' 模式或模拟模式,则跳过后续处理,调用下一个 AnteHandler
}
// 步骤 2: 检查交易是否包含 clob 消息
isSingleClobMsgTx, err := IsSingleClobMsgTx(ctx, tx)
if err != nil {
return ctx, err // 如果出现错误,则返回错误
}
if !isSingleClobMsgTx {
return next(ctx, tx, simulate) // 如果交易不包含 clob 消息,则跳过后续处理,调用下一个 AnteHandler
}
msgs := tx.GetMsgs()
var msg = msgs[0]
// 步骤 3: 设置请求级别的日志标签
ctx = log.AddPersistentTagsToLogger(ctx,
log.Module, log.Clob,
log.Callback, lib.TxMode(ctx),
log.BlockHeight, ctx.BlockHeight()+1,
log.Msg, msg,
)
switch msg := msg.(type) {
case *types.MsgCancelOrder:
ctx = log.AddPersistentTagsToLogger(ctx,
log.Handler, log.CancelOrder,
)
// 步骤 4: 处理取消订单消息
if msg.OrderId.IsStatefulOrder() {
err = cd.clobKeeper.CancelStatefulOrder(ctx, msg) // 调用取消状态订单的处理函数
} else {
// 如果不是状态订单,并且处于 'ReCheckTx' 模式,则跳过后续处理,调用下一个 AnteHandler
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}
// 否则,调用取消短期订单的处理函数
err = cd.clobKeeper.CancelShortTermOrder(ctx, msg)
}
// 记录订单取消操作的日志信息
log.DebugLog(ctx, "Received new order cancellation",
log.Tx, cometbftlog.NewLazySprintf("%X", tmhash.Sum(ctx.TxBytes())),
log.Error, err,
)
case *types.MsgPlaceOrder:
ctx = log.AddPersistentTagsToLogger(ctx,
log.Handler, log.PlaceOrder,
)
// 步骤 5: 处理下单消息
if msg.Order.OrderId.IsStatefulOrder() {
err = cd.clobKeeper.PlaceStatefulOrder(ctx, msg) // 调用下单状态订单的处理函数
// 记录新状态订单的日志信息
log.DebugLog(ctx, "Received new stateful order",
log.Tx, cometbftlog.NewLazySprintf("%X", tmhash.Sum(ctx.TxBytes())),
log.OrderHash, cometbftlog.NewLazySprintf("%X", msg.Order.GetOrderHash()),
log.Error, err,
)
} else {
// 如果不是状态订单,并且处于 'ReCheckTx' 模式,则跳过后续处理,调用下一个 AnteHandler
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}
var orderSizeOptimisticallyFilledFromMatchingQuantums satypes.BaseQuantums
var status types.OrderStatus
// 否则,调用下单短期订单的处理函数
orderSizeOptimisticallyFilledFromMatchingQuantums, status, err = cd.clobKeeper.PlaceShortTermOrder(
ctx,
msg,
)
// 记录新短期订单的日志信息
log.DebugLog(ctx, "Received new short term order",
log.Tx, cometbftlog.NewLazySprintf("%X", tmhash.Sum(ctx.TxBytes())),
log.OrderHash, cometbftlog.NewLazySprintf("%X", msg.Order.GetOrderHash()),
log.OrderStatus, status,
log.OrderSizeOptimisticallyFilledFromMatchingQuantums, orderSizeOptimisticallyFilledFromMatchingQuantums,
log.Error, err,
)
}
}
if err != nil {
return ctx, err // 如果在处理过程中出现错误,则返回错误
}
return next(ctx, tx, simulate) // 调用下一个 AnteHandler 并返回上下文和错误
}
```
## [PlaceStatefulOrder](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/keeper/orders.go#L256)
1. **确保订单不是短期订单:** 首先,函数检查订单是否为短期订单。如果是短期订单,将引发错误,因为此函数专门设计用于处理有状态订单。
2. **执行有状态验证:** 接下来,函数对订单执行有状态验证。此步骤检查订单是否满足有状态验证的标准,以确保其合法性。
3. **验证账户的权益层限制:** 函数验证放置订单是否会超过账户的权益层限制。这确保了账户的权益层限制保持在合理范围内。
4. **执行抵押检查:** 为防止滥用系统资源和垃圾信息,函数执行抵押检查,以确保订单的抵押金额足够。如果检查失败,将返回错误。
5. **写入状态:** 最后,根据执行上下文,函数将订单写入系统的状态或临时内存存储。如果在“DeliverTx”模式下,订单将写入已提交的状态和内存存储;否则,将写入临时存储。
```
// PlaceStatefulOrder 执行订单验证、权益层限制检查、抵押检查,并将订单写入状态和内存存储。订单不会放置在订单簿上。
//
// 如果满足以下任何条件,则会返回错误:
// - 标准有状态验证失败。
// - 超过权益层限制。
// - 抵押检查失败。
//
// 请注意,此方法根据上下文有条件地更新状态。这是为了区分在DeliverTx期间更新提交状态的情况,以及在CheckTx期间修改的未提交状态。
//
// 如果提供的订单不是有状态订单,则此方法将引发恐慌。
func (k Keeper) PlaceStatefulOrder(
ctx sdk.Context,
msg *types.MsgPlaceOrder,
) (err error) {
defer func() {
if err != nil {
// 在错误发生时递增错误计数器
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, metrics.PlaceStatefulOrder, metrics.Error, metrics.Count},
1,
[]gometrics.Label{
metrics.GetLabelForStringValue(metrics.Callback, metrics.GetCallbackMetricFromCtx(ctx)),
},
)
} else {
// 在成功时递增成功计数器
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, metrics.PlaceStatefulOrder, metrics.Success, metrics.Count},
1,
[]gometrics.Label{
metrics.GetLabelForStringValue(metrics.Callback, metrics.GetCallbackMetricFromCtx(ctx)),
},
)
}
}()
// 1. 确保订单不是短期订单
order := msg.Order
order.OrderId.MustBeStatefulOrder()
// 2. 执行订单的有状态验证
if err := k.PerformStatefulOrderValidation(
ctx,
&order,
// 请注意,在有状态订单验证期间不使用块高度。
0,
false,
); err != nil {
return err
}
// 3. 检查添加订单是否会超过账户的权益层限制
if err := k.ValidateSubaccountEquityTierLimitForNewOrder(ctx, order); err != nil {
return err
}
// 4. 执行订单抵押检查以防止滥用
_, successPerSubaccountUpdate := k.AddOrderToOrderbookCollatCheck(
ctx,
order.GetClobPairId(),
map[satypes.SubaccountId][]types.PendingOpenOrder{
order.OrderId.SubaccountId: {
{
RemainingQuantums: order.GetBaseQuantums(),
IsBuy: order.IsBuy(),
Subticks: order.GetOrderSubticks(),
ClobPairId: order.GetClobPairId(),
},
},
},
)
if !successPerSubaccountUpdate[order.OrderId.SubaccountId].IsSuccess() {
return errorsmod.Wrapf(
types.ErrStatefulOrderCollateralizationCheckFailed,
"PlaceStatefulOrder: order (%+v), result (%s)",
order,
successPerSubaccountUpdate[order.OrderId.SubaccountId].String(),
)
}
// 5. 如果在DeliverTx模式下,将订单写入已提交状态和内存存储,否则将写入未提交状态
if lib.IsDeliverTxMode(ctx) {
// 将有状态订单写入状态和内存存储
k.SetLongTermOrderPlacement(ctx, order, lib.MustConvertIntegerToUint32(ctx.BlockHeight()))
k.MustAddOrderToStatefulOrdersTimeSlice(
ctx,
order.MustGetUnixGoodTilBlockTime(),
order.GetOrderId(),
)
} else {
// 将有状态订单写入临时存储,PerformStatefulOrderValidation将确保订单不存在,
// 这将防止在DeliverTx期间添加未提交的有状态订单。
k.MustAddUncommittedStatefulOrderPlacement(ctx, msg)
// TODO(DEC-1238):支持有状态订单替代,删除未提交的订单取消。
// 这将允许在DeliverTx期间执行放置+取消+放置+取消+...的循环,我们当前在DeliverTx期间不允许这样做。
}
return nil
}
```
## [AddOrderToOrderbookCollatCheck](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/keeper/orders.go#L933)
用来检查抵押物
这部分的代码在底层间接的调用了:
[UpdateSubaccounts](https://hackmd.io/K37UkwteTyKq4aaXm7YQRQ?view#UpdateSubaccounts)的检查逻辑