From 1fd358149f770fbfbcf1396c8ace58c27c418f58 Mon Sep 17 00:00:00 2001 From: Jonathan Downing Date: Wed, 22 May 2024 17:06:08 -0500 Subject: [PATCH] Bugfix: convert external address to contextual address --- common/address.go | 11 +++++++ core/state_processor.go | 59 ++++++++++++++++++++++++------------ core/worker.go | 16 +++++++--- internal/quaiapi/quai_api.go | 7 +++-- 4 files changed, 66 insertions(+), 27 deletions(-) diff --git a/common/address.go b/common/address.go index 2ab5bbe8c1..2f89fb6f0f 100644 --- a/common/address.go +++ b/common/address.go @@ -407,3 +407,14 @@ func MakeErrQiAddress(addr string) error { func (a Address) MixedcaseAddress() MixedcaseAddress { return NewMixedcaseAddress(a) } + +func IsConversionOutput(a []byte, nodeLocation Location) bool { + if len(a) != AddressLength { + return false + } + // Extract nibbles + lowerNib := a[0] & 0x0F // Lower 4 bits + upperNib := (a[0] & 0xF0) >> 4 // Upper 4 bits, shifted right + + return Location{upperNib, lowerNib}.Equal(nodeLocation) && a[1] <= 127 +} diff --git a/core/state_processor.go b/core/state_processor.go index 92c0e88656..ea86218799 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -639,6 +639,7 @@ func ValidateQiTxInputs(tx *types.Transaction, chain ChainContext, statedb *stat } totalQitIn := big.NewInt(0) addresses := make(map[common.AddressBytes]struct{}) + inputs := make(map[uint]uint64) for _, txIn := range tx.TxIn() { utxo := statedb.GetUTXO(txIn.PreviousOutPoint.TxHash, txIn.PreviousOutPoint.Index) if utxo == nil { @@ -668,6 +669,24 @@ func ValidateQiTxInputs(tx *types.Transaction, chain ChainContext, statedb *stat return nil, errors.New(str) } totalQitIn.Add(totalQitIn, types.Denominations[denomination]) + inputs[uint(denomination)]++ + } + outputs := make(map[uint]uint64) + for _, txOut := range tx.TxOut() { + if txOut.Denomination > types.MaxDenomination { + str := fmt.Sprintf("transaction output value of %v is "+ + "higher than max allowed value of %v", + txOut.Denomination, + types.MaxDenomination) + return nil, errors.New(str) + } + outputs[uint(txOut.Denomination)]++ + if common.IsConversionOutput(txOut.Address, location) { // Qi->Quai conversion + outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated + } + } + if err := CheckDenominations(inputs, outputs); err != nil { + return nil, err } return totalQitIn, nil @@ -725,6 +744,8 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext, totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Add to total conversion output for aggregation delete(addresses, toAddr.Bytes20()) continue + } else if toAddr.IsInQuaiLedgerScope() { + return nil, fmt.Errorf("tx [%v] emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex()) } if !toAddr.Location().Equal(location) { // This output creates an ETX @@ -925,8 +946,11 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, ch return nil, 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 + outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated delete(addresses, toAddr.Bytes20()) continue + } else if toAddr.IsInQuaiLedgerScope() { + return nil, nil, fmt.Errorf("tx %v emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex()) } if !toAddr.Location().Equal(location) { // This output creates an ETX @@ -1017,26 +1041,9 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, ch etxs = append(etxs, &etxInner) txFeeInQit.Sub(txFeeInQit, txFeeInQit) // Fee goes entirely to gas to pay for conversion } - // Go through all denominations largest to smallest, check if the input exists as the output, if not, convert it to the respective number of bills for the next smallest denomination, then repeat the check. Subtract the 'carry' when the outputs match the carry for that denomination. - carries := make(map[uint]uint64) - for i := types.MaxDenomination; i >= 0; i-- { - if carry, exists := carries[uint(i)]; exists { - if carry > inputs[uint(i)] { - return nil, nil, fmt.Errorf("tx attempts to combine smaller denominations into larger one for denomination %d", i) - } - inputs[uint(i)] -= carry - } - if outputs[uint(i)] > inputs[uint(i)] { - diff := new(big.Int).SetUint64(outputs[uint(i)] - inputs[uint(i)]) - if i == 0 { - return nil, nil, fmt.Errorf("tx attempts to combine smaller denominations into larger one for denomination %d", i) - } - carries[uint(i-1)] += diff.Mul(diff, (new(big.Int).Div(types.Denominations[uint8(i)], types.Denominations[uint8(i-1)]))).Uint64() - } else if outputs[uint(i)] < inputs[uint(i)] { - continue - } + if err := CheckDenominations(inputs, outputs); err != nil { + return nil, nil, err } - // Ensure the transaction signature is valid if checkSig { var finalKey *btcec.PublicKey @@ -1063,6 +1070,20 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, ch return txFeeInQit, etxs, nil } +// Go through all denominations largest to smallest, check if the input exists as the output, if not, convert it to the respective number of bills for the next smallest denomination, then repeat the check. Subtract the 'carry' when the outputs match the carry for that denomination. +func CheckDenominations(inputs, outputs map[uint]uint64) error { + carries := make(map[uint]uint64) + for i := types.MaxDenomination; i >= 0; i-- { + if outputs[uint(i)] <= inputs[uint(i)]+carries[uint(i)] { + diff := new(big.Int).SetUint64((inputs[uint(i)] + carries[uint(i)]) - (outputs[uint(i)])) + carries[uint(i-1)] += diff.Mul(diff, (new(big.Int).Div(types.Denominations[uint8(i)], types.Denominations[uint8(i-1)]))).Uint64() + } else { + return fmt.Errorf("tx attempts to combine smaller denominations into larger one for denomination %d", i) + } + } + return nil +} + // Apply State func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*types.Log, error) { nodeCtx := p.hc.NodeCtx() diff --git a/core/worker.go b/core/worker.go index 0caab89ca1..6ccaa2f783 100644 --- a/core/worker.go +++ b/core/worker.go @@ -1468,7 +1468,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { addresses := make(map[common.AddressBytes]struct{}) totalQitIn := big.NewInt(0) utxosDelete := make([]types.OutPoint, 0) - inputDenominations := make(map[uint8]uint64) + inputs := make(map[uint]uint64) for _, txIn := range tx.TxIn() { utxo := env.state.GetUTXO(txIn.PreviousOutPoint.TxHash, txIn.PreviousOutPoint.Index) if utxo == nil { @@ -1487,7 +1487,6 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { types.MaxDenomination) return errors.New(str) } - inputDenominations[denomination] += 1 // Check for duplicate addresses. This also checks for duplicate inputs. if _, exists := addresses[common.AddressBytes(utxo.Address)]; exists { return errors.New("Duplicate address in QiTx inputs: " + common.AddressBytes(utxo.Address).String()) @@ -1495,6 +1494,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { addresses[common.AddressBytes(utxo.Address)] = struct{}{} totalQitIn.Add(totalQitIn, types.Denominations[denomination]) utxosDelete = append(utxosDelete, txIn.PreviousOutPoint) + inputs[uint(denomination)]++ } var ETXRCount int var ETXPCount int @@ -1504,7 +1504,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { utxosCreate := make(map[types.OutPoint]*types.UtxoEntry) conversion := false var convertAddress common.Address - outputDenominations := make(map[uint8]uint64) + outputs := make(map[uint]uint64) for txOutIdx, txOut := range tx.TxOut() { if txOutIdx > types.MaxOutputIndex { return errors.New("transaction has too many outputs") @@ -1516,7 +1516,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { types.MaxDenomination) return errors.New(str) } - outputDenominations[txOut.Denomination] += 1 + outputs[uint(txOut.Denomination)] += 1 totalQitOut.Add(totalQitOut, types.Denominations[txOut.Denomination]) toAddr := common.BytesToAddress(txOut.Address, location) if _, exists := addresses[toAddr.Bytes20()]; exists { @@ -1532,9 +1532,12 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { return fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), txOut.Denomination) } totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Add to total conversion output for aggregation - outputDenominations[txOut.Denomination] -= 1 // This output no longer exists because it has been aggregated + outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated delete(addresses, toAddr.Bytes20()) continue + } else if toAddr.IsInQuaiLedgerScope() { + w.logger.Error(fmt.Errorf("tx %032x emits UTXO with To address not in the Qi ledger scope", tx.Hash())) + return fmt.Errorf("tx %032x emits UTXO with To address not in the Qi ledger scope", tx.Hash()) } if !toAddr.Location().Equal(location) { // This output creates an ETX @@ -1639,6 +1642,9 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { return err } } + if err := CheckDenominations(inputs, outputs); err != nil { + return err + } // We could add signature verification here, but it's already checked in the mempool and the signature can't be changed, so duplication is largely unnecessary return nil } diff --git a/internal/quaiapi/quai_api.go b/internal/quaiapi/quai_api.go index 643cfc0c1d..e886b10c57 100644 --- a/internal/quaiapi/quai_api.go +++ b/internal/quaiapi/quai_api.go @@ -151,7 +151,8 @@ func (s *PublicBlockChainQuaiAPI) GetQiBalance(ctx context.Context, address comm if !address.ValidChecksum() { return nil, errors.New("address has invalid checksum") } - utxos, err := s.b.UTXOsByAddress(ctx, address.Address()) + addr := common.HexToAddress(address.Original(), s.b.NodeLocation()) + utxos, err := s.b.UTXOsByAddress(ctx, addr) if utxos == nil || err != nil { return nil, err } @@ -385,7 +386,7 @@ func (s *PublicBlockChainQuaiAPI) GetCode(ctx context.Context, address common.Mi if state == nil || err != nil { return nil, err } - internal, err := address.Address().InternalAndQuaiAddress() + internal, err := common.HexToAddress(address.Original(), s.b.NodeLocation()).InternalAddress() if err != nil { return nil, err } @@ -411,7 +412,7 @@ func (s *PublicBlockChainQuaiAPI) GetStorageAt(ctx context.Context, address comm if state == nil || err != nil { return nil, err } - internal, err := address.Address().InternalAndQuaiAddress() + internal, err := common.HexToAddress(address.Original(), s.b.NodeLocation()).InternalAddress() if err != nil { return nil, err }