Skip to content

Commit

Permalink
Initial utxo types, db methods, and accessors
Browse files Browse the repository at this point in the history
  • Loading branch information
0xalank authored and jdowning100 committed Feb 21, 2024
1 parent 29b6242 commit fe63809
Show file tree
Hide file tree
Showing 19 changed files with 903 additions and 34 deletions.
55 changes: 55 additions & 0 deletions core/rawdb/accessors_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1488,3 +1488,58 @@ func DeleteInboundEtxs(db ethdb.KeyValueWriter, hash common.Hash) {
log.Global.WithField("err", err).Fatal("Failed to delete inbound etxs")
}
}

func WriteUtxo(db ethdb.KeyValueWriter, hash common.Hash, utxo *types.UtxoEntry) {
data, err := rlp.EncodeToBytes(utxo)
if err != nil {
log.Global.Fatal("Failed to RLP encode inbound etxs", "err", err)
}
if err := db.Put(utxoKey(hash), data); err != nil {
log.Global.Fatal("Failed to store badHashesList", "err", err)
}
}

func ReadUtxo(db ethdb.Reader, hash common.Hash) *types.UtxoEntry {
// Try to look up the data in leveldb.
data, _ := db.Get(utxoKey(hash))
if len(data) == 0 {
return nil
}
utxo := new(types.UtxoEntry)
if err := rlp.Decode(bytes.NewReader(data), utxo); err != nil {
log.Global.Error("Invalid utxo RLP", "utxo", utxo, "err", err)
return nil
}
return utxo
}

// DeleteUtxo deletes utxos from the database
func DeleteUtxo(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Delete(utxoKey(hash)); err != nil {
log.Global.Fatal("Failed to delete utxo", "err", err)
}
}

func WriteSpentUTXOs(db ethdb.KeyValueWriter, hash common.Hash, spentUTXOs *[]types.SpentTxOut) {
data, err := rlp.EncodeToBytes(spentUTXOs)
if err != nil {
log.Global.Fatal("Failed to RLP encode spent utxos", "err", err)
}
if err := db.Put(spentUTXOsKey(hash), data); err != nil {
log.Global.Fatal("Failed to store spent utxos", "err", err)
}
}

func ReadSpentUTXOs(db ethdb.Reader, hash common.Hash) []types.SpentTxOut {
// Try to look up the data in leveldb.
data, _ := db.Get(spentUTXOsKey(hash))
if len(data) == 0 {
return nil
}
spentUTXOs := []types.SpentTxOut{}
if err := rlp.Decode(bytes.NewReader(data), &spentUTXOs); err != nil {
log.Global.Error("Invalid spent utxos RLP", "err", err)
return nil
}
return spentUTXOs
}
12 changes: 11 additions & 1 deletion core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ var (
phBodyPrefix = []byte("pc") // phBodyPrefix + hash -> []common.Hash + Td
terminiPrefix = []byte("tk") //terminiPrefix + hash -> []common.Hash
badHashesListPrefix = []byte("bh")
inboundEtxsPrefix = []byte("ie") // inboundEtxsPrefix + hash -> types.Transactions
inboundEtxsPrefix = []byte("ie") // inboundEtxsPrefix + hash -> types.Transactions
utxoPrefix = []byte("ut") // outpointPrefix + hash -> types.Outpoint
spentUTXOsPrefix = []byte("sutxo") // spentUTXOsPrefix + hash -> []types.SpentTxOut

blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
Expand Down Expand Up @@ -321,3 +323,11 @@ func bloomKey(hash common.Hash) []byte {
func inboundEtxsKey(hash common.Hash) []byte {
return append(inboundEtxsPrefix, hash.Bytes()...)
}

func utxoKey(hash common.Hash) []byte {
return append(utxoPrefix, hash.Bytes()...)
}

func spentUTXOsKey(hash common.Hash) []byte {
return append(spentUTXOsPrefix, hash.Bytes()...)
}
11 changes: 11 additions & 0 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,17 @@ func (b *Block) SubManifest() BlockManifest { return b.subManifest }

func (b *Block) Header() *Header { return b.header }

func (b *Block) QiTransactions() []*Transaction {
// TODO: cache the UTXO loop
qiTxs := make([]*Transaction, 0)
for _, t := range b.Transactions() {
if t.Type() == QiTxType {
qiTxs = append(qiTxs, t)
}
}
return qiTxs
}

// Body returns the non-header content of the block.
func (b *Block) Body() *Body {
return &Body{b.transactions, b.uncles, b.extTransactions, b.subManifest}
Expand Down
6 changes: 4 additions & 2 deletions core/types/external_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,14 @@ func (tx *ExternalTx) etxGasPrice() *big.Int { panic("external TX does
func (tx *ExternalTx) etxGasTip() *big.Int { panic("external TX does not have etxGasTip") }
func (tx *ExternalTx) etxData() []byte { panic("external TX does not have etxData") }
func (tx *ExternalTx) etxAccessList() AccessList { panic("external TX does not have etxAccessList") }
func (tx *ExternalTx) txIn() []*TxIn { panic("external TX does not have txIn") }
func (tx *ExternalTx) txOut() []*TxOut { panic("external TX does not have txOut") }

func (tx *ExternalTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *ExternalTx) getEcdsaSignatureValues() (v, r, s *big.Int) {
// Signature values are ignored for external transactions
return nil, nil, nil
}

func (tx *ExternalTx) setSignatureValues(chainID, v, r, s *big.Int) {
func (tx *ExternalTx) setEcdsaSignatureValues(chainID, v, r, s *big.Int) {
// Signature values are ignored for external transactions
}
6 changes: 4 additions & 2 deletions core/types/internal_to_external_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ func (tx *InternalToExternalTx) etxGasPrice() *big.Int { return tx.ETXGasPri
func (tx *InternalToExternalTx) etxGasTip() *big.Int { return tx.ETXGasTip }
func (tx *InternalToExternalTx) etxData() []byte { return tx.ETXData }
func (tx *InternalToExternalTx) etxAccessList() AccessList { return tx.ETXAccessList }
func (tx *InternalToExternalTx) txIn() []*TxIn { panic("internalToExternal TX does not have txIn") }
func (tx *InternalToExternalTx) txOut() []*TxOut { panic("internalToExternal TX does not have txOut") }

func (tx *InternalToExternalTx) etxSender() common.Address {
panic("internalToExternal TX does not have etxSender")
Expand All @@ -128,10 +130,10 @@ func (tx *InternalToExternalTx) etxIndex() uint16 {
panic("internalToExternal TX does not have etxIndex")
}

func (tx *InternalToExternalTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *InternalToExternalTx) getEcdsaSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S
}

func (tx *InternalToExternalTx) setSignatureValues(chainID, v, r, s *big.Int) {
func (tx *InternalToExternalTx) setEcdsaSignatureValues(chainID, v, r, s *big.Int) {
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
}
6 changes: 4 additions & 2 deletions core/types/internal_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ func (tx *InternalTx) originatingTxHash() common.Hash {
panic("internal TX does not have originatingTxHash")
}
func (tx *InternalTx) etxIndex() uint16 { panic("internal TX does not have etxIndex") }
func (tx *InternalTx) txIn() []*TxIn { panic("internal TX does not have txIn") }
func (tx *InternalTx) txOut() []*TxOut { panic("internal TX does not have txOut") }

func (tx *InternalTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *InternalTx) getEcdsaSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S
}

func (tx *InternalTx) setSignatureValues(chainID, v, r, s *big.Int) {
func (tx *InternalTx) setEcdsaSignatureValues(chainID, v, r, s *big.Int) {
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
}
64 changes: 64 additions & 0 deletions core/types/qi_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package types

import (
"math/big"

"github.com/dominant-strategies/go-quai/common"
)

type QiTx struct {
ChainID *big.Int // replay protection
TxIn []*TxIn
TxOut []*TxOut
}

// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *QiTx) copy() TxData {
cpy := &QiTx{
ChainID: new(big.Int),
}
if tx.ChainID != nil {
cpy.ChainID.Set(tx.ChainID)
}
cpy.TxIn = make([]*TxIn, len(tx.TxIn))
cpy.TxOut = make([]*TxOut, len(tx.TxOut))
copy(cpy.TxIn, tx.TxIn)
copy(cpy.TxOut, tx.TxOut)
return cpy
}

// accessors for innerTx.
func (tx *QiTx) txType() byte { return QiTxType }
func (tx *QiTx) chainID() *big.Int { return tx.ChainID }
func (tx *QiTx) protected() bool { return true }
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) gas() uint64 { panic("Qi TX does not have gas") }
func (tx *QiTx) gasFeeCap() *big.Int { panic("Qi TX does not have gasFeeCap") }
func (tx *QiTx) gasTipCap() *big.Int { panic("Qi TX does not have gasTipCap") }
func (tx *QiTx) gasPrice() *big.Int { panic("Qi TX does not have gasPrice") }
func (tx *QiTx) value() *big.Int { panic("Qi TX does not have value") }
func (tx *QiTx) nonce() uint64 { panic("Qi TX does not have nonce") }
func (tx *QiTx) to() *common.Address { panic("Qi TX does not have to") }
func (tx *QiTx) etxGasLimit() uint64 { panic("Qi TX does not have etxGasLimit") }
func (tx *QiTx) etxGasPrice() *big.Int { panic("Qi TX does not have etxGasPrice") }
func (tx *QiTx) etxGasTip() *big.Int { panic("Qi TX does not have etxGasTip") }
func (tx *QiTx) etxData() []byte { panic("Qi TX does not have etxData") }
func (tx *QiTx) etxAccessList() AccessList { panic("Qi TX does not have etxAccessList") }
func (tx *QiTx) originatingTxHash() common.Hash {
panic("Qi TX does not have originatingTxHash")
}
func (tx *QiTx) etxIndex() uint16 { panic("Qi TX does not have etxIndex") }
func (tx *QiTx) etxSender() common.Address {
panic("Qi TX does not have etxSender")
}
func (tx *QiTx) txIn() []*TxIn { return tx.TxIn }
func (tx *QiTx) txOut() []*TxOut { return tx.TxOut }

func (tx *QiTx) getEcdsaSignatureValues() (v, r, s *big.Int) {
panic("Qi TX does not have ECDSA signature values")
}

func (tx *QiTx) setEcdsaSignatureValues(chainID, v, r, s *big.Int) {
panic("Qi TX does not have ECDSA signature values")
}
33 changes: 33 additions & 0 deletions core/types/stxo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package types

// SpentTxOut contains a spent transaction output and potentially additional
// contextual information such as whether or not it was contained in a coinbase
// transaction, the version of the transaction it was contained in, and which
// block height the containing transaction was included in. As described in
// the comments above, the additional contextual information will only be valid
// when this spent txout is spending the last unspent output of the containing
// transaction.
type SpentTxOut struct {
// Amount is the amount of the output.
Amount uint64

// Address is the output holder's address.
Address []byte

// Height is the height of the block containing the creating tx.
Height uint64

// Denotes if the creating tx is a coinbase.
IsCoinBase bool
}

// countSpentOutputs returns the number of utxos the passed block spends.
func CountSpentOutputs(block *Block) int {
// Exclude the coinbase transaction since it can't spend anything.
var numSpent int
for _, tx := range block.QiTransactions()[1:] {
numSpent += len(tx.TxIn())
}

return numSpent
}
29 changes: 20 additions & 9 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
InternalTxType = iota
ExternalTxType
InternalToExternalTxType
QiTxType
)

// Transaction is a Quai transaction.
Expand Down Expand Up @@ -74,7 +75,7 @@ func (tx *Transaction) SetInner(inner TxData) {

// TxData is the underlying data of a transaction.
//
// This is implemented by InternalTx, ExternalTx and InternalToExternal.
// This is implemented by InternalTx, ExternalTx, InternalToExternal, and UtxoTx.
type TxData interface {
txType() byte // returns the type ID
copy() TxData // creates a deep copy and initializes all fields
Expand All @@ -97,9 +98,11 @@ type TxData interface {
etxSender() common.Address
originatingTxHash() common.Hash
etxIndex() uint16
txIn() []*TxIn
txOut() []*TxOut

rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int)
getEcdsaSignatureValues() (v, r, s *big.Int)
setEcdsaSignatureValues(chainID, v, r, s *big.Int)
}

// ProtoEncode serializes tx into the Quai Proto Transaction format
Expand Down Expand Up @@ -129,7 +132,7 @@ func (tx *Transaction) ProtoEncode() (*ProtoTransaction, error) {
}
protoTx.GasFeeCap = tx.GasFeeCap().Bytes()
protoTx.GasTipCap = tx.GasTipCap().Bytes()
V, R, S := tx.RawSignatureValues()
V, R, S := tx.GetEcdsaSignatureValues()
protoTx.V = V.Bytes()
protoTx.R = R.Bytes()
protoTx.S = S.Bytes()
Expand All @@ -153,7 +156,7 @@ func (tx *Transaction) ProtoEncode() (*ProtoTransaction, error) {
protoTx.To = tx.To().Bytes()
protoTx.GasFeeCap = tx.GasFeeCap().Bytes()
protoTx.GasTipCap = tx.GasTipCap().Bytes()
V, R, S := tx.RawSignatureValues()
V, R, S := tx.GetEcdsaSignatureValues()
protoTx.V = V.Bytes()
protoTx.R = R.Bytes()
protoTx.S = S.Bytes()
Expand Down Expand Up @@ -402,6 +405,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
var inner InternalToExternalTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case QiTxType:
var inner QiTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
default:
return nil, ErrTxTypeNotSupported
}
Expand Down Expand Up @@ -488,6 +495,10 @@ func (tx *Transaction) OriginatingTxHash() common.Hash { return tx.inner.origina

func (tx *Transaction) ETXIndex() uint16 { return tx.inner.etxIndex() }

func (tx *Transaction) TxOut() []*TxOut { return tx.inner.txOut() }

func (tx *Transaction) TxIn() []*TxIn { return tx.inner.txIn() }

func (tx *Transaction) IsInternalToExternalTx() (inner *InternalToExternalTx, ok bool) {
inner, ok = tx.inner.(*InternalToExternalTx)
return
Expand Down Expand Up @@ -522,10 +533,10 @@ func (tx *Transaction) Cost() *big.Int {
return total
}

// RawSignatureValues returns the V, R, S signature values of the transaction.
// GetEcdsaSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller.
func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
return tx.inner.rawSignatureValues()
func (tx *Transaction) GetEcdsaSignatureValues() (v, r, s *big.Int) {
return tx.inner.getEcdsaSignatureValues()
}

// GasFeeCapCmp compares the fee cap of two transactions.
Expand Down Expand Up @@ -653,7 +664,7 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e
return nil, err
}
cpy := tx.inner.copy()
cpy.setSignatureValues(signer.ChainID(), v, r, s)
cpy.setEcdsaSignatureValues(signer.ChainID(), v, r, s)
return &Transaction{inner: cpy, time: tx.time}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion core/types/transaction_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (s SignerV1) Sender(tx *Transaction) (common.Address, error) {
if tx.Type() == ExternalTxType { // External TX does not have a signature
return tx.inner.(*ExternalTx).Sender, nil
}
V, R, S := tx.RawSignatureValues()
V, R, S := tx.GetEcdsaSignatureValues()
// DynamicFee txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected signatures.
V = new(big.Int).Add(V, big.NewInt(27))
Expand Down
Loading

0 comments on commit fe63809

Please sign in to comment.