Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions db/el_accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,40 @@ import (

func InsertElAccount(ctx context.Context, dbTx *sqlx.Tx, account *dbtypes.ElAccount) (uint64, error) {
var id uint64
query := EngineQuery(map[dbtypes.DBEngineType]string{
dbtypes.DBEnginePgsql: "INSERT INTO el_accounts (address, funder_id, funded, is_contract, last_nonce, last_block_uid) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id",
dbtypes.DBEngineSqlite: "INSERT INTO el_accounts (address, funder_id, funded, is_contract, last_nonce, last_block_uid) VALUES ($1, $2, $3, $4, $5, $6)",
})

if DbEngine == dbtypes.DBEnginePgsql {
// Use ON CONFLICT with a no-op update to return the existing id on conflict.
query := "INSERT INTO el_accounts (address, funder_id, funded, is_contract, last_nonce, last_block_uid) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (address) DO UPDATE SET address = excluded.address RETURNING id"
err := dbTx.QueryRowContext(ctx, query, account.Address, account.FunderID, account.Funded, account.IsContract, account.LastNonce, account.LastBlockUid).Scan(&id)
if err != nil {
return 0, err
}
} else {
// SQLite: use INSERT OR IGNORE to skip on duplicate address, then look up the id.
query := "INSERT OR IGNORE INTO el_accounts (address, funder_id, funded, is_contract, last_nonce, last_block_uid) VALUES ($1, $2, $3, $4, $5, $6)"
result, err := dbTx.ExecContext(ctx, query, account.Address, account.FunderID, account.Funded, account.IsContract, account.LastNonce, account.LastBlockUid)
if err != nil {
return 0, err
}
lastID, err := result.LastInsertId()

rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
id = uint64(lastID)

if rowsAffected > 0 {
lastID, err := result.LastInsertId()
if err != nil {
return 0, err
}
id = uint64(lastID)
} else {
// Row already exists, look up the existing id.
err := dbTx.QueryRowContext(ctx, "SELECT id FROM el_accounts WHERE address = $1", account.Address).Scan(&id)
if err != nil {
return 0, fmt.Errorf("failed to get existing account id: %w", err)
}
}
}
return id, nil
}
Expand Down
4 changes: 2 additions & 2 deletions db/el_token_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,13 @@ func GetElTokenTransfersByAccountIDCombined(ctx context.Context, accountID uint6
SELECT block_uid, tx_hash, tx_pos, tx_idx, token_id, token_type, token_index,
from_id, to_id, amount, amount_raw
FROM (
(SELECT block_uid, tx_hash, tx_pos, tx_idx, token_id, token_type, token_index,
SELECT * FROM (SELECT block_uid, tx_hash, tx_pos, tx_idx, token_id, token_type, token_index,
from_id, to_id, amount, amount_raw
FROM el_token_transfers WHERE from_id = $1%s
ORDER BY block_uid DESC NULLS LAST
LIMIT $%d)
UNION ALL
(SELECT block_uid, tx_hash, tx_pos, tx_idx, token_id, token_type, token_index,
SELECT * FROM (SELECT block_uid, tx_hash, tx_pos, tx_idx, token_id, token_type, token_index,
from_id, to_id, amount, amount_raw
FROM el_token_transfers WHERE to_id = $2 AND from_id != $3%s
ORDER BY block_uid DESC NULLS LAST
Expand Down
4 changes: 2 additions & 2 deletions db/el_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,14 +293,14 @@ func GetElTransactionsByAccountIDCombined(ctx context.Context, accountID uint64,
method_id, gas_limit, gas_used, gas_price, tip_price, blob_count, block_number,
tx_type, tx_index, eff_gas_price
FROM (
(SELECT block_uid, tx_hash, from_id, to_id, nonce, reverted, amount, amount_raw,
SELECT * FROM (SELECT block_uid, tx_hash, from_id, to_id, nonce, reverted, amount, amount_raw,
method_id, gas_limit, gas_used, gas_price, tip_price, blob_count, block_number,
tx_type, tx_index, eff_gas_price
FROM el_transactions WHERE from_id = $1
ORDER BY block_uid DESC NULLS LAST
LIMIT $4)
UNION ALL
(SELECT block_uid, tx_hash, from_id, to_id, nonce, reverted, amount, amount_raw,
SELECT * FROM (SELECT block_uid, tx_hash, from_id, to_id, nonce, reverted, amount, amount_raw,
method_id, gas_limit, gas_used, gas_price, tip_price, blob_count, block_number,
tx_type, tx_index, eff_gas_price
FROM el_transactions WHERE to_id = $2 AND from_id != $3
Expand Down
156 changes: 155 additions & 1 deletion handlers/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func Transaction(w http.ResponseWriter, r *http.Request) {
"transaction/statechanges.html",
"transaction/transfers.html",
"transaction/internaltxs.html",
"transaction/authorizations.html",
"transaction/blobs.html",
)
notfoundTemplateFiles := append(layoutTemplateFiles,
Expand Down Expand Up @@ -103,7 +104,7 @@ func Transaction(w http.ResponseWriter, r *http.Request) {

if pageData.TxNotFound {
data := InitPageData(w, r, "blockchain", "/tx", "Transaction not found", notfoundTemplateFiles)
data.Data = pageData
data.Data = "notfound"
w.Header().Set("Content-Type", "text/html")
handleTemplateError(w, r, "transaction.go", "Transaction", "notFound", templates.GetTemplate(notfoundTemplateFiles...).ExecuteTemplate(w, "layout", data))
return
Expand Down Expand Up @@ -423,8 +424,13 @@ func buildTransactionPageDataFromDB(ctx context.Context, pageData *models.Transa
loadTransactionTransfersFromData(ctx, pageData, transfers)
case "internaltxs":
loadTransactionInternalTxsFromBlockdb(ctx, pageData, tx.BlockUid)
computeInternalTxIndent(pageData)
case "statechanges":
loadTransactionStateChangesFromBlockdb(ctx, pageData, tx.BlockUid)
case "authorizations":
if pageData.TxType == ethtypes.SetCodeTxType && len(pageData.Authorizations) > 0 {
resolveAuthorizationValidity(ctx, pageData, tx.BlockUid)
}
}
}

Expand Down Expand Up @@ -532,6 +538,11 @@ func buildTransactionPageDataFromEL(ctx context.Context, pageData *models.Transa
// Blob hashes
pageData.BlobCount = uint32(len(ethTx.BlobHashes()))

// Authorization data for type 4 (EIP-7702) transactions
if ethTx.Type() == ethtypes.SetCodeTxType {
loadAuthorizationData(pageData, ethTx)
}

// Generate RLP and JSON
if rlpData, err := ethTx.MarshalBinary(); err == nil {
pageData.TxRLP = "0x" + hex.EncodeToString(rlpData)
Expand Down Expand Up @@ -1162,6 +1173,35 @@ func loadTransactionInternalTxsFromDB(ctx context.Context, pageData *models.Tran
}
}

// computeInternalTxIndent sets InternalTxIndentPx based on the maximum
// nesting depth so that deeply nested trees compress to ~300px total.
func computeInternalTxIndent(pageData *models.TransactionPageData) {
if len(pageData.InternalTxs) == 0 {
return
}

var maxDepth uint16
for _, itx := range pageData.InternalTxs {
if itx.Depth > maxDepth {
maxDepth = itx.Depth
}
}

if maxDepth == 0 {
pageData.InternalTxIndentPx = 18.0
return
}

indent := 300.0 / float64(maxDepth)
if indent > 18.0 {
indent = 18.0
}
if indent < 2.0 {
indent = 2.0
}
pageData.InternalTxIndentPx = indent
}

func loadTransactionTransfersFromData(ctx context.Context, pageData *models.TransactionPageData, transfers []*dbtypes.ElTokenTransfer) {
if len(transfers) == 0 {
return
Expand Down Expand Up @@ -1294,6 +1334,11 @@ func loadFullTransactionData(ctx context.Context, pageData *models.TransactionPa
if ethTx.Type() == 3 && len(ethTx.BlobHashes()) > 0 {
loadBlobData(pageData, &ethTx, blockData)
}

// Load authorization data for type 4 (EIP-7702) transactions
if ethTx.Type() == ethtypes.SetCodeTxType {
loadAuthorizationData(pageData, &ethTx)
}
}

// loadBlobData populates blob-related data for type 3 (blob) transactions.
Expand Down Expand Up @@ -1407,3 +1452,112 @@ func applyCallTargetResolution(ctx context.Context, pageData *models.Transaction
pageData.MethodID = res.MethodID
}
}

// loadAuthorizationData extracts EIP-7702 authorization list entries from a
// parsed transaction and populates pageData.Authorizations.
func loadAuthorizationData(
pageData *models.TransactionPageData,
ethTx *ethtypes.Transaction,
) {
authList := ethTx.SetCodeAuthorizations()
if len(authList) == 0 {
return
}

pageData.Authorizations = make(
[]*models.TransactionPageDataAuthorization,
len(authList),
)

for i := range authList {
auth := &authList[i]
entry := &models.TransactionPageDataAuthorization{
Index: uint32(i),
DelegateAddr: auth.Address.Bytes(),
}

if authority, err := auth.Authority(); err == nil {
entry.AuthorityAddr = authority.Bytes()
entry.AuthorityOk = true
}

pageData.Authorizations[i] = entry
}
}

// resolveAuthorizationValidity loads state diffs from blockdb and checks
// whether each EIP-7702 authorization was actually applied on-chain.
// An authorization is considered applied when the authority address has a code
// change whose post-state matches the delegation designator (0xef0100 + delegate).
func resolveAuthorizationValidity(
ctx context.Context,
pageData *models.TransactionPageData,
blockUid uint64,
) {
if pageData.DataStatus&dbtypes.ElBlockDataStateChanges == 0 {
return
}

if blockdb.GlobalBlockDb == nil || !blockdb.GlobalBlockDb.SupportsExecData() {
return
}

slot := blockUid >> 16
blockRoot := pageData.BlockRoot
if len(blockRoot) == 0 {
return
}

ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

sections, err := blockdb.GlobalBlockDb.GetExecDataTxSections(
ctx, slot, blockRoot, pageData.TxHash,
bdbtypes.ExecDataSectionStateChange,
)
if err != nil || sections == nil || sections.StateChangeData == nil {
return
}

uncompData, err := snappy.Decode(nil, sections.StateChangeData)
if err != nil {
return
}

var accounts []bdbtypes.StateChangeAccount
if err := dynssz.GetGlobalDynSsz().UnmarshalSSZ(&accounts, uncompData); err != nil {
return
}

// Build a lookup of address -> post-code for accounts with code changes.
type codeInfo struct {
postCode []byte
}
codeByAddr := make(map[common.Address]codeInfo, len(accounts))
for i := range accounts {
a := &accounts[i]
if (a.Flags & bdbtypes.StateChangeFlagCodeChanged) != 0 {
codeByAddr[a.Address] = codeInfo{postCode: a.PostCode}
}
}

for _, auth := range pageData.Authorizations {
if !auth.AuthorityOk {
continue
}

authorityAddr := common.BytesToAddress(auth.AuthorityAddr)
ci, found := codeByAddr[authorityAddr]
if !found {
auth.Applied = 2 // not applied
continue
}

delegateAddr, ok := ethtypes.ParseDelegation(ci.postCode)
if ok && delegateAddr == common.BytesToAddress(auth.DelegateAddr) {
auth.Applied = 1 // applied
} else {
auth.Applied = 2 // not applied
}
}
}
9 changes: 5 additions & 4 deletions templates/address/address.html
Original file line number Diff line number Diff line change
Expand Up @@ -235,18 +235,19 @@ <h5 class="mb-0"><i class="fas fa-coins me-2"></i>Token Holdings ({{ .TokenBalan

function onTabSelected(event) {
event.preventDefault();
var tabId = event.target.getAttribute('data-lazy-tab');
var link = event.currentTarget;
var tabId = link.getAttribute('data-lazy-tab');
var tabEl = document.getElementById(tabId);
currentTab = tabId;

if (!$(tabEl).data("loaded")) {
loadTabContent(tabEl, event.target.getAttribute('href'));
loadTabContent(tabEl, link.getAttribute('href'));
}

var tab = new bootstrap.Tab(event.target);
var tab = new bootstrap.Tab(link);
tab.show();

window.history.replaceState(null, document.title, "/address/" + addressStr + event.target.getAttribute('href'));
window.history.replaceState(null, document.title, "/address/" + addressStr + link.getAttribute('href'));
}

function loadTabContent(tabEl, url) {
Expand Down
7 changes: 4 additions & 3 deletions templates/consolidations/consolidations.html
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,11 @@ <h6 class="m-2 text-muted">This table displays pending consolidations in the que

function onTabSelected(event) {
event.preventDefault();
var tabId = event.target.getAttribute('data-lazy-tab');
var link = event.currentTarget;
var tabId = link.getAttribute('data-lazy-tab');
var tabEl = document.getElementById(tabId);
if (!$(tabEl).data("loaded")) {
$.get(event.target.getAttribute('href') + "&lazy=true", function(data) {
$.get(link.getAttribute('href') + "&lazy=true", function(data) {
$(tabEl).html(data);
$(tabEl).data("loaded", true);
explorer.initControls();
Expand All @@ -362,7 +363,7 @@ <h6 class="m-2 text-muted">This table displays pending consolidations in the que
var tab = new bootstrap.Tab(tabEl);
tab.show();

window.history.replaceState(null, document.title, "/validators/consolidations" + event.target.getAttribute('href'));
window.history.replaceState(null, document.title, "/validators/consolidations" + link.getAttribute('href'));
}
});
</script>
Expand Down
7 changes: 4 additions & 3 deletions templates/deposits/deposits.html
Original file line number Diff line number Diff line change
Expand Up @@ -490,10 +490,11 @@ <h6 class="m-2 text-muted">This table displays deposits waiting to be activated

function onTabSelected(event) {
event.preventDefault();
var tabId = event.target.getAttribute('data-lazy-tab');
var link = event.currentTarget;
var tabId = link.getAttribute('data-lazy-tab');
var tabEl = document.getElementById(tabId);
if (!$(tabEl).data("loaded")) {
$.get(event.target.getAttribute('href') + "&lazy=true", function(data) {
$.get(link.getAttribute('href') + "&lazy=true", function(data) {
$(tabEl).html(data);
$(tabEl).data("loaded", true);
explorer.initControls();
Expand All @@ -503,7 +504,7 @@ <h6 class="m-2 text-muted">This table displays deposits waiting to be activated
var tab = new bootstrap.Tab(tabEl);
tab.show();

window.history.replaceState(null, document.title, "/validators/deposits" + event.target.getAttribute('href'));
window.history.replaceState(null, document.title, "/validators/deposits" + link.getAttribute('href'));
}
});
</script>
Expand Down
7 changes: 4 additions & 3 deletions templates/exits/exits.html
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,11 @@ <h6 class="m-2 text-muted">This table displays validators that are currently exi

function onTabSelected(event) {
event.preventDefault();
var tabId = event.target.getAttribute('data-lazy-tab');
var link = event.currentTarget;
var tabId = link.getAttribute('data-lazy-tab');
var tabEl = document.getElementById(tabId);
if (!$(tabEl).data("loaded")) {
$.get(event.target.getAttribute('href') + "&lazy=true", function(data) {
$.get(link.getAttribute('href') + "&lazy=true", function(data) {
$(tabEl).html(data);
$(tabEl).data("loaded", true);
explorer.initControls();
Expand All @@ -292,7 +293,7 @@ <h6 class="m-2 text-muted">This table displays validators that are currently exi
var tab = new bootstrap.Tab(tabEl);
tab.show();

window.history.replaceState(null, document.title, "/validators/exits" + event.target.getAttribute('href'));
window.history.replaceState(null, document.title, "/validators/exits" + link.getAttribute('href'));
}
});
</script>
Expand Down
Loading