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 }