github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/rpc/get_transaction.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
     9  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/prefunds"
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc/query"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk"
    13  	"github.com/ethereum/go-ethereum"
    14  )
    15  
    16  func (conn *Connection) GetTransactionByNumberAndId(bn base.Blknum, txid base.Txnum) (*types.Transaction, error) {
    17  	if conn.StoreReadable() {
    18  		// walk.Cache_Transactions
    19  		tx := &types.Transaction{
    20  			BlockNumber:      bn,
    21  			TransactionIndex: txid,
    22  		}
    23  		if err := conn.Store.Read(tx, nil); err == nil {
    24  			// success
    25  			return tx, nil
    26  		}
    27  	}
    28  
    29  	trans, err := conn.getTransactionFromRpc(notAHash, notAHash, bn, txid)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	blockTs := conn.GetBlockTimestamp(bn)
    35  	receipt, err := conn.GetReceipt(bn, txid, blockTs)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	trans.Timestamp = blockTs
    41  	trans.HasToken = types.IsTokenFunction(trans.Input)
    42  	trans.GasUsed = receipt.GasUsed
    43  	trans.IsError = receipt.IsError
    44  	trans.Receipt = &receipt
    45  
    46  	isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
    47  	if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Transactions] {
    48  		_ = conn.Store.Write(trans, nil)
    49  	}
    50  
    51  	return trans, nil
    52  }
    53  
    54  func (conn *Connection) GetTransactionByAppearance(app *types.Appearance, fetchTraces bool) (*types.Transaction, error) {
    55  	theApp := types.Appearance{
    56  		BlockNumber:      app.BlockNumber,
    57  		TransactionIndex: app.TransactionIndex,
    58  	}
    59  	if !app.Address.IsZero() {
    60  		theApp.Address = app.Address
    61  	}
    62  
    63  	bn := base.Blknum(theApp.BlockNumber)
    64  	txid := base.Txnum(theApp.TransactionIndex)
    65  
    66  	if conn.StoreReadable() {
    67  		// walk.Cache_Transactions
    68  		tx := &types.Transaction{
    69  			BlockNumber:      bn,
    70  			TransactionIndex: txid,
    71  		}
    72  		if err := conn.Store.Read(tx, nil); err == nil {
    73  			// success
    74  			if fetchTraces {
    75  				traces, err := conn.GetTracesByTransactionHash(tx.Hash.Hex(), tx)
    76  				if err != nil {
    77  					return nil, err
    78  				}
    79  				tx.Traces = traces
    80  			}
    81  			return tx, nil
    82  		}
    83  	}
    84  
    85  	blockTs := conn.GetBlockTimestamp(bn)
    86  	if bn == 0 {
    87  		if tx, err := conn.GetTransactionPrefundByApp(&theApp); err != nil {
    88  			return nil, err
    89  		} else {
    90  			tx.Timestamp = blockTs
    91  			isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
    92  			if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Transactions] {
    93  				_ = conn.Store.Write(tx, nil)
    94  			}
    95  			return tx, nil
    96  		}
    97  	} else if txid == types.BlockReward || txid == types.MisconfigReward || txid == types.ExternalReward {
    98  		if tx, err := conn.GetTransactionRewardByTypeAndApp(types.BlockReward, &theApp); err != nil {
    99  			return nil, err
   100  		} else {
   101  			tx.Timestamp = blockTs
   102  			isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
   103  			if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Transactions] {
   104  				_ = conn.Store.Write(tx, nil)
   105  			}
   106  			return tx, nil
   107  		}
   108  	} else if txid == types.UncleReward {
   109  		if tx, err := conn.GetTransactionRewardByTypeAndApp(types.UncleReward, &theApp); err != nil {
   110  			return nil, err
   111  		} else {
   112  			tx.Timestamp = blockTs
   113  			isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
   114  			if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Transactions] {
   115  				_ = conn.Store.Write(tx, nil)
   116  			}
   117  			return tx, nil
   118  		}
   119  	} else if txid == types.WithdrawalAmt {
   120  		if tx, err := conn.GetTransactionRewardByTypeAndApp(types.WithdrawalAmt, &theApp); err != nil {
   121  			return nil, err
   122  		} else {
   123  			tx.Timestamp = blockTs
   124  			isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
   125  			if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Transactions] {
   126  				_ = conn.Store.Write(tx, nil)
   127  			}
   128  			return tx, nil
   129  		}
   130  	}
   131  
   132  	receipt, err := conn.GetReceipt(bn, txid, blockTs)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	trans, err := conn.getTransactionFromRpc(notAHash, notAHash, bn, txid)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	trans.Timestamp = blockTs
   143  	trans.HasToken = types.IsTokenFunction(trans.Input)
   144  	trans.GasUsed = receipt.GasUsed
   145  	trans.IsError = receipt.IsError
   146  	trans.Receipt = &receipt
   147  
   148  	isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
   149  	if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Transactions] {
   150  		_ = conn.Store.Write(trans, nil)
   151  	}
   152  
   153  	if fetchTraces {
   154  		traces, err := conn.GetTracesByTransactionHash(trans.Hash.Hex(), trans)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  		trans.Traces = traces
   159  	}
   160  
   161  	return trans, err
   162  }
   163  
   164  // GetTransactionAppByHash returns a transaction's appearance if it's a valid transaction
   165  func (conn *Connection) GetTransactionAppByHash(hash string) (types.Appearance, error) {
   166  	var ret types.Appearance
   167  	if trans, err := conn.getTransactionFromRpc(notAHash, base.HexToHash(hash), base.NOPOSN, base.NOPOSN); err != nil {
   168  		return ret, err
   169  	} else {
   170  		ret.BlockNumber = uint32(trans.BlockNumber)
   171  		ret.TransactionIndex = uint32(trans.TransactionIndex)
   172  		return ret, nil
   173  	}
   174  }
   175  
   176  // GetTransactionHashByNumberAndID returns a transaction's hash if it's a valid transaction
   177  func (conn *Connection) GetTransactionHashByNumberAndID(bn base.Blknum, txId base.Txnum) (base.Hash, error) {
   178  	if trans, err := conn.getTransactionFromRpc(notAHash, notAHash, bn, txId); err != nil {
   179  		return base.Hash{}, err
   180  	} else {
   181  		return trans.Hash, nil
   182  	}
   183  }
   184  
   185  // GetTransactionHashByHash returns a transaction's hash if it's a valid transaction, an empty string otherwise
   186  func (conn *Connection) GetTransactionHashByHash(hash string) (string, error) {
   187  	if trans, err := conn.getTransactionFromRpc(notAHash, base.HexToHash(hash), base.NOPOSN, base.NOPOSN); err != nil {
   188  		return "", err
   189  	} else {
   190  		return trans.Hash.Hex(), nil
   191  	}
   192  }
   193  
   194  // GetTransactionHashByHashAndID returns a transaction's hash if it's a valid transaction
   195  func (conn *Connection) GetTransactionHashByHashAndID(hash string, txId base.Txnum) (string, error) {
   196  	if trans, err := conn.getTransactionFromRpc(base.HexToHash(hash), notAHash, base.NOPOSN, txId); err != nil {
   197  		return "", err
   198  	} else {
   199  		return trans.Hash.Hex(), nil
   200  	}
   201  }
   202  
   203  func (conn *Connection) GetTransactionPrefundByApp(theApp *types.Appearance) (tx *types.Transaction, err error) {
   204  	// TODO: performance - This loads and then drops the file every time it's called. Quite slow.
   205  	// TODO: performance - in the old C++ we stored these values in a pre fundAddrMap so that given a txid in block zero
   206  	// TODO: performance - we knew which address was granted allocation at that transaction.
   207  	prefundPath := prefunds.GetPrefundPath(conn.Chain)
   208  	if prefundMap, err := prefunds.LoadPrefundMap(conn.Chain, prefundPath); err != nil {
   209  		return nil, err
   210  	} else {
   211  		var blockHash base.Hash
   212  		var ts base.Timestamp
   213  		if block, err := conn.GetBlockHeaderByNumber(0); err != nil {
   214  			return nil, err
   215  		} else {
   216  			blockHash = block.Hash
   217  			ts = block.Timestamp
   218  		}
   219  
   220  		entry := (*prefundMap)[theApp.Address]
   221  		if entry.Address == theApp.Address {
   222  			ret := types.Transaction{
   223  				BlockHash:        blockHash,
   224  				BlockNumber:      base.Blknum(theApp.BlockNumber),
   225  				TransactionIndex: base.Txnum(theApp.TransactionIndex),
   226  				Timestamp:        ts,
   227  				From:             base.PrefundSender,
   228  				To:               theApp.Address,
   229  				Value:            entry.Prefund,
   230  			}
   231  			return &ret, nil
   232  		}
   233  	}
   234  	return nil, errors.New("not found")
   235  }
   236  
   237  // TODO: This is not cross-chain correct nor does it work properly for post-merge
   238  
   239  func (conn *Connection) GetTransactionRewardByTypeAndApp(rt base.Txnum, theApp *types.Appearance) (*types.Transaction, error) {
   240  	if block, err := conn.GetBlockBodyByNumber(base.Blknum(theApp.BlockNumber)); err != nil {
   241  		return nil, err
   242  	} else {
   243  		if rt == types.WithdrawalAmt {
   244  			tx := &types.Transaction{
   245  				BlockNumber:      base.Blknum(theApp.BlockNumber),
   246  				TransactionIndex: base.Txnum(theApp.TransactionIndex),
   247  				Timestamp:        block.Timestamp,
   248  				From:             base.WithdrawalSender,
   249  				To:               theApp.Address,
   250  			}
   251  			return tx, nil
   252  		}
   253  
   254  		if uncles, err := conn.GetUncleBodiesByNumber(base.Blknum(theApp.BlockNumber)); err != nil {
   255  			return nil, err
   256  		} else {
   257  			var blockReward = base.NewWei(0)
   258  			var nephewReward = base.NewWei(0)
   259  			var feeReward = base.NewWei(0)
   260  			var uncleReward = base.NewWei(0)
   261  
   262  			sender := theApp.Address
   263  			bn := base.Blknum(theApp.BlockNumber)
   264  			blockReward = conn.getBlockReward(bn)
   265  			switch rt {
   266  			case types.BlockReward:
   267  				if block.Miner == theApp.Address {
   268  					sender = base.BlockRewardSender
   269  					nUncles := len(uncles)
   270  					if nUncles > 0 {
   271  						nephewReward = new(base.Wei).Mul(blockReward, base.NewWei(int64(nUncles)))
   272  						nephewReward.Div(nephewReward, base.NewWei(32))
   273  					}
   274  					for _, tx := range block.Transactions {
   275  						gp := base.NewWei(int64(tx.GasPrice))
   276  						gu := base.NewWei(int64(tx.Receipt.GasUsed))
   277  						feeReward = feeReward.Add(feeReward, gp.Mul(gp, gu))
   278  					}
   279  				} else {
   280  					blockReward = base.NewWei(0)
   281  				}
   282  			case types.UncleReward:
   283  				for _, uncle := range uncles {
   284  					if uncle.Miner == theApp.Address {
   285  						sender = base.UncleRewardSender
   286  						if bn < uncle.BlockNumber+6 {
   287  							diff := (uncle.BlockNumber + 8 - bn) // positive since +6 < bn
   288  							uncleReward = new(base.Wei).Mul(blockReward, base.NewWei(int64(diff)))
   289  							uncleReward.Div(uncleReward, base.NewWei(8))
   290  						}
   291  					}
   292  				}
   293  				if block.Miner == theApp.Address {
   294  					sender = base.BlockRewardSender // if it's both, it's the block reward
   295  					// The uncle miner may also have been the miner of the block
   296  					if minerTx, err := conn.GetTransactionRewardByTypeAndApp(types.BlockReward, theApp); err != nil {
   297  						return nil, err
   298  					} else {
   299  						blockReward = &minerTx.Rewards.Block
   300  						nephewReward = &minerTx.Rewards.Nephew
   301  						feeReward = &minerTx.Rewards.TxFee
   302  					}
   303  				} else {
   304  					blockReward = base.NewWei(0)
   305  				}
   306  			case types.NephewReward:
   307  				fallthrough
   308  			case types.TxFeeReward:
   309  				fallthrough
   310  			default:
   311  				return nil, errors.New("invalid reward type")
   312  			}
   313  
   314  			rewards, total := types.NewReward(blockReward, nephewReward, feeReward, uncleReward)
   315  			tx := &types.Transaction{
   316  				BlockNumber:      base.Blknum(theApp.BlockNumber),
   317  				TransactionIndex: base.Txnum(theApp.TransactionIndex),
   318  				BlockHash:        block.Hash,
   319  				Timestamp:        block.Timestamp,
   320  				From:             sender,
   321  				To:               theApp.Address,
   322  				Value:            total,
   323  				Rewards:          &rewards,
   324  			}
   325  			return tx, nil
   326  		}
   327  	}
   328  }
   329  
   330  // GetTransactionCountInBlock returns the number of transactions in a block
   331  func (conn *Connection) GetTransactionCountInBlock(bn base.Blknum) (uint64, error) {
   332  	// TODO: Can we use our Query here?
   333  	if ec, err := conn.getClient(); err != nil {
   334  		return 0, err
   335  	} else {
   336  		defer ec.Close()
   337  
   338  		block, err := ec.BlockByNumber(context.Background(), base.BiFromBn(bn))
   339  		if err != nil {
   340  			return 0, err
   341  		}
   342  
   343  		cnt, err := ec.TransactionCount(context.Background(), block.Hash())
   344  		return uint64(cnt), err
   345  	}
   346  }
   347  
   348  var (
   349  	notAHash = base.Hash{}
   350  )
   351  
   352  func (conn *Connection) getTransactionFromRpc(blkHash base.Hash, txHash base.Hash, bn base.Blknum, txid base.Txnum) (*types.Transaction, error) {
   353  	method := "eth_getTransactionByBlockNumberAndIndex"
   354  	params := query.Params{fmt.Sprintf("0x%x", bn), fmt.Sprintf("0x%x", txid)}
   355  	if txHash != notAHash {
   356  		method = "eth_getTransactionByHash"
   357  		params = query.Params{txHash.Hex()}
   358  	} else if blkHash != notAHash {
   359  		method = "eth_getTransactionByBlockHashAndIndex"
   360  		params = query.Params{blkHash.Hex(), fmt.Sprintf("0x%x", txid)}
   361  	}
   362  
   363  	if trans, err := query.Query[types.Transaction](conn.Chain, method, params); err != nil {
   364  		return &types.Transaction{
   365  			Receipt: &types.Receipt{},
   366  		}, err
   367  	} else if trans.Hash.IsZero() {
   368  		return &types.Transaction{
   369  			Receipt: &types.Receipt{},
   370  		}, ethereum.NotFound
   371  	} else {
   372  		return trans, nil
   373  	}
   374  }