From 498b1af4ceb213486d62360b998bf821d817dbf1 Mon Sep 17 00:00:00 2001 From: Jonathan Downing Date: Fri, 12 Apr 2024 16:40:30 -0500 Subject: [PATCH] Check if Qi address is in-scope before adding to UTXO set --- common/address.go | 42 +++++++++++++++++++++++++++++++++++++++++ core/genesis.go | 4 +++- core/state/statedb.go | 7 ++++++- core/state_processor.go | 12 +++++++++--- core/worker.go | 14 +++++++++++--- 5 files changed, 71 insertions(+), 8 deletions(-) diff --git a/common/address.go b/common/address.go index 067561f73b..c894f476ba 100644 --- a/common/address.go +++ b/common/address.go @@ -75,6 +75,36 @@ func (a Address) InternalAndQuaiAddress() (InternalAddress, error) { return *internal, nil } +func CheckIfBytesAreInternalAndQiAddress(b []byte, nodeLocation Location) error { + if len(b) != AddressLength { + return fmt.Errorf("address %s is not %d bytes long", hexutil.Encode(b), AddressLength) + } + if !IsInChainScope(b, nodeLocation) { + return ErrInvalidScope + } + if AddressBytes(b).IsInQuaiLedgerScope() { + return ErrQuaiAddress + } + return nil +} + +func (a Address) InternalAndQiAddress() (InternalAddress, error) { + if a.inner == nil { + return InternalAddress{}, ErrNilInner + } + if a.IsInQuaiLedgerScope() { + return InternalAddress{}, ErrQuaiAddress + } + internal, ok := a.inner.(*InternalAddress) + if !ok { + return InternalAddress{}, ErrInvalidScope + } + if internal == nil { + return InternalAddress{}, ErrNilInner + } + return *internal, nil +} + func (a Address) IsInQiLedgerScope() bool { // The first bit of the second byte is set if the address is in the Qi ledger return a.Bytes()[1] > 127 @@ -358,6 +388,18 @@ func (a AddressBytes) hex() []byte { return buf[:] } +func (a AddressBytes) Location() *Location { + // Extract nibbles + lowerNib := a[0] & 0x0F // Lower 4 bits + upperNib := (a[0] & 0xF0) >> 4 // Upper 4 bits, shifted right + return &Location{upperNib, lowerNib} +} + +func (a AddressBytes) IsInQuaiLedgerScope() bool { + // The first bit of the second byte is not set if the address is in the Quai ledger + return a[1] <= 127 +} + func MakeErrQiAddress(addr string) error { return fmt.Errorf("address %s is in Qi ledger scope and is not in Quai ledger scope", addr) } diff --git a/core/genesis.go b/core/genesis.go index fe039e5236..3362464c7e 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -533,6 +533,8 @@ func AddGenesisUtxos(state *state.StateDB, nodeLocation common.Location, logger Denomination: uint8(utxo.Denomination), } - state.CreateUTXO(hash, uint16(utxo.Index), newUtxo) + if err := state.CreateUTXO(hash, uint16(utxo.Index), newUtxo); err != nil { + panic(fmt.Sprintf("Failed to create genesis UTXO: %v", err)) + } } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 39f5ca8844..5dfae106d2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -575,10 +575,14 @@ func (s *StateDB) DeleteUTXO(txHash common.Hash, outputIndex uint16) { } // CreateUTXO explicitly creates a UTXO entry. -func (s *StateDB) CreateUTXO(txHash common.Hash, outputIndex uint16, utxo *types.UtxoEntry) { +func (s *StateDB) CreateUTXO(txHash common.Hash, outputIndex uint16, utxo *types.UtxoEntry) error { if metrics_config.MetricsEnabled() { defer func(start time.Time) { stateMetrics.WithLabelValues("CreateUTXO").Add(float64(time.Since(start))) }(time.Now()) } + // This check is largely redundant, but it's a good sanity check. Might be removed in the future. + if err := common.CheckIfBytesAreInternalAndQiAddress(utxo.Address, s.nodeLocation); err != nil { + return err + } data, err := rlp.EncodeToBytes(utxo) if err != nil { panic(fmt.Errorf("can't encode UTXO entry at %x: %v", txHash, err)) @@ -586,6 +590,7 @@ func (s *StateDB) CreateUTXO(txHash common.Hash, outputIndex uint16, utxo *types if err := s.utxoTrie.TryUpdate(utxoKey(txHash, outputIndex), data); err != nil { s.setError(fmt.Errorf("createUTXO (%x) error: %v", txHash, err)) } + return nil } func (s *StateDB) CommitUTXOs() (common.Hash, error) { diff --git a/core/state_processor.go b/core/state_processor.go index f6d2dd3ede..a4319d3c44 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -307,7 +307,9 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) return nil, nil, nil, nil, 0, fmt.Errorf("etx %032x emits UTXO with value %d greater than max denomination", tx.Hash(), tx.Value().Int64()) } // There are no more checks to be made as the ETX is worked so add it to the set - statedb.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Int64()), tx.To().Bytes()))) + if err := statedb.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Int64()), tx.To().Bytes()))); err != nil { + return nil, nil, nil, nil, 0, fmt.Errorf("could not create UTXO for etx %032x: %w", tx.Hash(), err) + } if err := gp.SubGas(params.CallValueTransferGas); err != nil { return nil, nil, nil, nil, 0, err } @@ -369,7 +371,9 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) return nil, nil, nil, nil, 0, fmt.Errorf("coinbase tx emits UTXO with To address not equal to block coinbase") } utxo := types.NewUtxoEntry(&txOut) - statedb.CreateUTXO(qiTransactions[0].Hash(), uint16(txOutIdx), utxo) + if err := statedb.CreateUTXO(qiTransactions[0].Hash(), uint16(txOutIdx), utxo); err != nil { + return nil, nil, nil, nil, 0, fmt.Errorf("could not create UTXO for coinbase tx %032x: %w", qiTransactions[0].Hash(), err) + } p.logger.WithFields(log.Fields{ "txHash": qiTransactions[0].Hash().Hex(), "txOutIdx": txOutIdx, @@ -596,7 +600,9 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, cu // This output creates a normal UTXO utxo := types.NewUtxoEntry(&txOut) if updateState { - statedb.CreateUTXO(tx.Hash(), uint16(txOutIdx), utxo) + if err := statedb.CreateUTXO(tx.Hash(), uint16(txOutIdx), utxo); err != nil { + return nil, nil, err + } } } } diff --git a/core/worker.go b/core/worker.go index 76c62a0284..0f299a1e98 100644 --- a/core/worker.go +++ b/core/worker.go @@ -729,7 +729,9 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t if tx.Value().Int64() > types.MaxDenomination { return nil, fmt.Errorf("tx %032x emits UTXO with value greater than max denomination", tx.Hash()) } - env.state.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Int64()), tx.To().Bytes()))) + if err := env.state.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Int64()), tx.To().Bytes()))); err != nil { + return nil, err + } gasUsed := env.wo.Header().GasUsed() gasUsed += params.CallValueTransferGas env.wo.Header().SetGasUsed(gasUsed) @@ -1468,7 +1470,11 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { env.state.DeleteUTXO(utxo.TxHash, utxo.Index) } for outPoint, utxo := range utxosCreate { - env.state.CreateUTXO(outPoint.TxHash, outPoint.Index, utxo) + if err := env.state.CreateUTXO(outPoint.TxHash, outPoint.Index, utxo); err != nil { + // This should never happen and will invalidate the block + log.Global.Errorf("Failed to create UTXO %032x:%d: %v", outPoint.TxHash, outPoint.Index, err) + return err + } } gasUsed = env.wo.Header().GasUsed() env.wo.Header().SetGasUsed(gasUsed + txGas) @@ -1518,7 +1524,9 @@ func createCoinbaseTxWithFees(header *types.WorkObject, fees *big.Int, state *st tx := types.NewTx(qiTx) for i, out := range qiTx.TxOut { // this may be unnecessary - state.CreateUTXO(tx.Hash(), uint16(i), types.NewUtxoEntry(&out)) + if err := state.CreateUTXO(tx.Hash(), uint16(i), types.NewUtxoEntry(&out)); err != nil { + return nil, err + } } return tx, nil }