Skip to content

Commit

Permalink
Added Wrapped Qi precompile functionality and added Data field to Qi …
Browse files Browse the repository at this point in the history
…tx type
  • Loading branch information
jdowning100 committed Dec 17, 2024
1 parent ef13892 commit 02e6ce8
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 19 deletions.
77 changes: 66 additions & 11 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,25 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
continue
}
} else if etx.EtxType() == types.WrappingQiType {
if len(etx.Data()) != common.AddressLength {
return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("wrapping Qi ETX %x has invalid data length", etx.Hash())
}
if etx.To() == nil {
return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("wrapping Qi ETX %x has no recipient", etx.Hash())
}
ownerContractAddr := common.BytesToAddress(etx.Data(), nodeLocation)
if err := vm.WrapQi(statedb, ownerContractAddr, *etx.To(), common.OneInternal(nodeLocation), etx.Value(), nodeLocation); err != nil {
return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("could not wrap Qi: %v", err)
}
if err := gp.SubGas(params.QiToQuaiConversionGas); err != nil {
return nil, nil, nil, nil, 0, 0, 0, nil, nil, err
}
*usedGas += params.QiToQuaiConversionGas
totalEtxGas += params.QiToQuaiConversionGas
continue
}

// check if the tx is a coinbase tx
Expand Down Expand Up @@ -1264,6 +1282,9 @@ func ValidateQiTxInputs(tx *types.Transaction, chain ChainContext, db ethdb.Read
if tx.ChainId().Cmp(signer.ChainID()) != 0 {
return nil, fmt.Errorf("tx %032x has wrong chain ID", tx.Hash())
}
if len(tx.Data()) != 0 && len(tx.Data()) != common.AddressLength {
return nil, fmt.Errorf("tx %v emits UTXO with invalid data length %d", tx.Hash().Hex(), len(tx.Data()))
}
totalQitIn := big.NewInt(0)
addresses := make(map[common.AddressBytes]struct{})
inputs := make(map[uint]uint64)
Expand Down Expand Up @@ -1332,6 +1353,7 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext,
totalQitOut := big.NewInt(0)
totalConvertQitOut := big.NewInt(0)
conversion := false
wrapping := false
pubKeys := make([]*btcec.PublicKey, 0, len(tx.TxIn()))
addresses := make(map[common.AddressBytes]struct{})
for _, txIn := range tx.TxIn() {
Expand Down Expand Up @@ -1367,14 +1389,22 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext,
}
addresses[toAddr.Bytes20()] = struct{}{}

if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai conversion
if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) == 0 { // Qi->Quai conversion
conversion = true
if currentHeader.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV2 && txOut.Denomination < params.MinQiConversionDenomination {
return nil, fmt.Errorf("tx %v emits UTXO with value %d less than minimum denomination %d", tx.Hash().Hex(), txOut.Denomination, params.MinQiConversionDenomination)
}
totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Add to total conversion output for aggregation
delete(addresses, toAddr.Bytes20())
continue
} else if toAddr.Location().Equal(location) && toAddr.IsInQiLedgerScope() && len(tx.Data()) != 0 { // Quai->Qi wrapping
ownerContract := common.BytesToAddress(tx.Data(), location)
if _, err := ownerContract.InternalAndQuaiAddress(); err != nil {
return nil, err
}
wrapping = true
totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Uses the same path as conversion but takes priority
delete(addresses, toAddr.Bytes20())
} else if toAddr.IsInQuaiLedgerScope() {
return nil, fmt.Errorf("tx [%v] emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex())
}
Expand Down Expand Up @@ -1433,11 +1463,13 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext,
if txFeeInQuai.Cmp(minimumFeeInQuai) < 0 {
return nil, fmt.Errorf("tx %032x has insufficient fee for base fee, have %d want %d", tx.Hash(), txFeeInQuai.Uint64(), minimumFeeInQuai.Uint64())
}
if conversion {
if currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 {
return nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64())
if conversion && currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 {
return nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64())
}
if conversion || wrapping {
if conversion && wrapping {
return nil, fmt.Errorf("tx %032x emits both a conversion and a wrapping UTXO", tx.Hash())
}

if currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 {
// Since this transaction contains a conversion, check if the required conversion gas is paid
// The user must pay this to the miner now, but it is only added to the block gas limit when the ETX is played in the destination
Expand Down Expand Up @@ -1501,6 +1533,9 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir
if currentHeader == nil || batch == nil || gp == nil || usedGas == nil || signer == nil || etxRLimit == nil || etxPLimit == nil {
return nil, nil, nil, errors.New("one of the parameters is nil"), nil
}
if len(tx.Data()) != 0 && len(tx.Data()) != common.AddressLength {
return nil, nil, nil, fmt.Errorf("tx %v emits UTXO with invalid data length %d", tx.Hash().Hex(), len(tx.Data())), nil
}
intrinsicGas := types.CalculateIntrinsicQiTxGas(tx, qiScalingFactor)
*usedGas += intrinsicGas
if err := gp.SubGas(intrinsicGas); err != nil {
Expand Down Expand Up @@ -1575,6 +1610,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir
totalQitOut := big.NewInt(0)
totalConvertQitOut := big.NewInt(0)
conversion := false
wrapping := false
var convertAddress common.Address
for txOutIdx, txOut := range tx.TxOut() {
// It would be impossible for a tx to have this many outputs based on block gas limit, but cap it here anyways
Expand All @@ -1600,7 +1636,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir
addresses[toAddr.Bytes20()] = struct{}{}
outputs[uint(txOut.Denomination)]++

if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai conversion
if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) == 0 { // Qi->Quai conversion
conversion = true
convertAddress = toAddr
if currentHeader.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV2 && txOut.Denomination < params.MinQiConversionDenomination {
Expand All @@ -1610,6 +1646,16 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir
outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated
delete(addresses, toAddr.Bytes20())
continue
} else if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) != 0 { // Wrapped Qi transaction
ownerContract := common.BytesToAddress(tx.Data(), location)
if _, err := ownerContract.InternalAndQuaiAddress(); err != nil {
return nil, nil, nil, err, nil
}
wrapping = true
convertAddress = toAddr
totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Uses the same path as conversion but takes priority
outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated
delete(addresses, toAddr.Bytes20())
} else if toAddr.IsInQuaiLedgerScope() {
return nil, nil, nil, fmt.Errorf("tx %v emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex()), nil
}
Expand Down Expand Up @@ -1684,9 +1730,18 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir
if txFeeInQuai.Cmp(minimumFeeInQuai) < 0 {
return nil, nil, nil, fmt.Errorf("tx %032x has insufficient fee for base fee, have %d want %d", tx.Hash(), txFeeInQuai.Uint64(), minimumFeeInQuai.Uint64()), nil
}
if conversion {
if currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 {
return nil, nil, nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()), nil
if conversion && currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 {
return nil, nil, nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()), nil
}
if conversion || wrapping {
if conversion && wrapping {
return nil, nil, nil, fmt.Errorf("tx %032x emits both a conversion and a wrapping UTXO", tx.Hash()), nil
}
etxType := types.ConversionType
data := []byte{}
if wrapping {
etxType = types.WrappingQiType
data = tx.Data()
}
var etxInner types.ExternalTx
if currentHeader.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV2 {
Expand All @@ -1702,7 +1757,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir
if ETXPCount > *etxPLimit {
return nil, nil, nil, fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, etxPLimit), nil
}
etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: types.ConversionType, OriginatingTxHash: tx.Hash(), Gas: remainingGas.Uint64()} // Value is in Qits not Denomination
etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: uint64(etxType), OriginatingTxHash: tx.Hash(), Gas: remainingGas.Uint64(), Data: data} // Value is in Qits not Denomination
} else {
// Since this transaction contains a conversion, check if the required conversion gas is paid
// The user must pay this to the miner now, but it is only added to the block gas limit when the ETX is played in the destination
Expand All @@ -1715,7 +1770,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir
if ETXPCount > *etxPLimit {
return nil, nil, nil, fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, etxPLimit), nil
}
etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: types.ConversionType, OriginatingTxHash: tx.Hash(), Gas: 0} // Value is in Qits not Denomination
etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: uint64(etxType), OriginatingTxHash: tx.Hash(), Gas: 0, Data: data} // Value is in Qits not Denomination
}
*usedGas += params.ETXGas
if err := gp.SubGas(params.ETXGas); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion core/types/qi_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type QiTx struct {
TxOut TxOuts `json:"txOuts"`

Signature *schnorr.Signature
Data []byte // Data is currently only used for wrapping Qi in the EVM

// Work fields
ParentHash *common.Hash
Expand Down Expand Up @@ -103,7 +104,7 @@ func (tx *QiTx) parentHash() *common.Hash { return tx.ParentHash
func (tx *QiTx) mixHash() *common.Hash { return tx.MixHash }
func (tx *QiTx) workNonce() *BlockNonce { return tx.WorkNonce }
func (tx *QiTx) accessList() AccessList { panic("Qi TX does not have accessList") }
func (tx *QiTx) data() []byte { panic("Qi TX does not have data") }
func (tx *QiTx) data() []byte { return tx.Data }
func (tx *QiTx) gas() uint64 { panic("Qi TX does not have gas") }
func (tx *QiTx) minerTip() *big.Int { panic("Qi TX does not have minerTip") }
func (tx *QiTx) gasPrice() *big.Int { panic("Qi TX does not have gasPrice") }
Expand Down
16 changes: 15 additions & 1 deletion core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const (
CoinbaseType
ConversionType
CoinbaseLockupType
WrappingQiType
)

const (
Expand Down Expand Up @@ -222,7 +223,11 @@ func (tx *Transaction) ProtoEncode() (*ProtoTransaction, error) {
workNonce := tx.WorkNonce().Uint64()
protoTx.WorkNonce = &workNonce
}

if tx.Data() == nil {
protoTx.Data = []byte{}
} else {
protoTx.Data = tx.Data()
}
}
return protoTx, nil
}
Expand Down Expand Up @@ -373,6 +378,9 @@ func (tx *Transaction) ProtoDecode(protoTx *ProtoTransaction, location common.Lo
if protoTx.ChainId == nil {
return errors.New("missing required field 'ChainId' in ProtoTransaction")
}
if protoTx.Data == nil {
return errors.New("missing required field 'Data' in ProtoTransaction")
}
var qiTx QiTx
qiTx.ChainID = new(big.Int).SetBytes(protoTx.GetChainId())

Expand Down Expand Up @@ -411,6 +419,7 @@ func (tx *Transaction) ProtoDecode(protoTx *ProtoTransaction, location common.Lo
nonce := BlockNonce(uint64ToByteArr(*protoTx.WorkNonce))
qiTx.WorkNonce = &nonce
}
qiTx.Data = protoTx.GetData()
tx.SetInner(&qiTx)

default:
Expand Down Expand Up @@ -454,6 +463,11 @@ func (tx *Transaction) ProtoEncodeTxSigningData() *ProtoTransaction {
protoTxSigningData.ChainId = tx.ChainId().Bytes()
protoTxSigningData.TxIns, _ = tx.TxIn().ProtoEncode()
protoTxSigningData.TxOuts, _ = tx.TxOut().ProtoEncode()
if tx.Data() == nil {
protoTxSigningData.Data = []byte{}
} else {
protoTxSigningData.Data = tx.Data()
}
}
return protoTxSigningData
}
Expand Down
Loading

0 comments on commit 02e6ce8

Please sign in to comment.