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
45 changes: 2 additions & 43 deletions daemon/algod/api/server/v2/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package v2

import (
"encoding/base64"
"fmt"
"strings"

Expand Down Expand Up @@ -559,21 +558,8 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
}
result.Disassembly = debug.lines
result.AppCallTrace = &debug.history
result.GlobalDelta = StateDeltaToStateDelta(delta.GlobalDelta)
if len(delta.LocalDeltas) > 0 {
localDeltas := make([]model.AccountStateDelta, 0, len(delta.LocalDeltas))
for k, v := range delta.LocalDeltas {
ldaddr, err2 := stxn.Txn.AddressByIndex(k, stxn.Txn.Sender)
if err2 != nil {
messages = append(messages, err2.Error())
}
localDeltas = append(localDeltas, model.AccountStateDelta{
Address: ldaddr.String(),
Delta: *StateDeltaToStateDelta(v),
})
}
result.LocalDeltas = &localDeltas
}
result.GlobalDelta = sliceOrNil(globalDeltaToStateDelta(delta.GlobalDelta))
result.LocalDeltas = sliceOrNil(localDeltasToLocalDeltas(delta, &stxn.Txn))

// ensure the program has not exceeded execution budget
cost := maxCurrentBudget - pooledAppBudget
Expand Down Expand Up @@ -620,33 +606,6 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
}
}

// StateDeltaToStateDelta converts basics.StateDelta to model.StateDelta
func StateDeltaToStateDelta(sd basics.StateDelta) *model.StateDelta {
if len(sd) == 0 {
return nil
}

gsd := make(model.StateDelta, 0, len(sd))
for k, v := range sd {
value := model.EvalDelta{Action: uint64(v.Action)}
if v.Action == basics.SetBytesAction {
bytesVal := base64.StdEncoding.EncodeToString([]byte(v.Bytes))
value.Bytes = &bytesVal
} else if v.Action == basics.SetUintAction {
uintVal := v.Uint
value.Uint = &uintVal
}
// basics.DeleteAction does not require Uint/Bytes
kv := model.EvalDeltaKeyValue{
Key: base64.StdEncoding.EncodeToString([]byte(k)),
Value: value,
}
gsd = append(gsd, kv)
}

return &gsd
}

// DeltaLogToLog base64 encode the logs
func DeltaLogToLog(logs []string) (*[][]byte, error) {
if len(logs) == 0 {
Expand Down
6 changes: 3 additions & 3 deletions daemon/algod/api/server/v2/dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1084,12 +1084,12 @@ func TestStateDeltaToStateDelta(t *testing.T) {
Action: basics.DeleteAction,
},
}
gsd := StateDeltaToStateDelta(sd)
require.Equal(t, 3, len(*gsd))
gsd := globalDeltaToStateDelta(sd)
require.Equal(t, 3, len(gsd))

var keys []string
// test with a loop because sd is a map and iteration order is random
for _, item := range *gsd {
for _, item := range gsd {
if item.Key == b64("byteskey") {
require.Equal(t, uint64(1), item.Value.Action)
require.Nil(t, item.Value.Uint)
Expand Down
20 changes: 9 additions & 11 deletions daemon/algod/api/server/v2/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error)
EncodedBlockCert(rnd basics.Round) (blk []byte, cert []byte, err error)
Block(rnd basics.Round) (blk bookkeeping.Block, err error)
AddressTxns(id basics.Address, r basics.Round) ([]transactions.SignedTxnWithAD, error)
TxnsFrom(id basics.Address, r basics.Round) ([]transactions.Transaction, error)
GetStateDeltaForRound(rnd basics.Round) (ledgercore.StateDelta, error)
GetTracer() logic.EvalTracer
}
Expand Down Expand Up @@ -228,18 +228,18 @@
default:
}

txns, err := txnFetcher.AddressTxns(transactions.StateProofSender, i)
txns, err := txnFetcher.TxnsFrom(transactions.StateProofSender, i)

Check warning on line 231 in daemon/algod/api/server/v2/handlers.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/v2/handlers.go#L231

Added line #L231 was not covered by tests
if err != nil {
return transactions.Transaction{}, err
}
for _, txn := range txns {
if txn.Txn.Type != protocol.StateProofTx {
if txn.Type != protocol.StateProofTx {

Check warning on line 236 in daemon/algod/api/server/v2/handlers.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/v2/handlers.go#L236

Added line #L236 was not covered by tests
continue
}

if txn.Txn.StateProofTxnFields.Message.FirstAttestedRound <= uint64(round) &&
uint64(round) <= txn.Txn.StateProofTxnFields.Message.LastAttestedRound {
return txn.Txn, nil
if txn.StateProofTxnFields.Message.FirstAttestedRound <= uint64(round) &&
uint64(round) <= txn.StateProofTxnFields.Message.LastAttestedRound {
return txn, nil

Check warning on line 242 in daemon/algod/api/server/v2/handlers.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/v2/handlers.go#L240-L242

Added lines #L240 - L242 were not covered by tests
}
}
}
Expand Down Expand Up @@ -1573,7 +1573,8 @@
response.CloseRewards = &txn.ApplyData.CloseRewards.Raw
response.AssetIndex = computeAssetIndexFromTxn(txn, v2.Node.LedgerForAPI())
response.ApplicationIndex = computeAppIndexFromTxn(txn, v2.Node.LedgerForAPI())
response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(txn)
response.LocalStateDelta = sliceOrNil(localDeltasToLocalDeltas(txn.ApplyData.EvalDelta, &txn.Txn.Txn))
response.GlobalStateDelta = sliceOrNil(globalDeltaToStateDelta(txn.ApplyData.EvalDelta.GlobalDelta))

Check warning on line 1577 in daemon/algod/api/server/v2/handlers.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/v2/handlers.go#L1576-L1577

Added lines #L1576 - L1577 were not covered by tests
response.Logs = convertLogs(txn)
response.Inners = convertInners(&txn)
}
Expand Down Expand Up @@ -1622,9 +1623,6 @@
return internalError(ctx, err, errFailedLookingUpTransactionPool, v2.Log)
}

// MatchAddress uses this to check FeeSink, we don't care about that here.
spec := transactions.SpecialAddresses{}

txnLimit := uint64(math.MaxUint64)
if max != nil && *max != 0 {
txnLimit = *max
Expand All @@ -1639,7 +1637,7 @@
}

// continue if we have an address filter and the address doesn't match the transaction.
if addrPtr != nil && !txn.Txn.MatchAddress(*addrPtr, spec) {
if addrPtr != nil && !txn.Txn.MatchAddress(*addrPtr) {

Check warning on line 1640 in daemon/algod/api/server/v2/handlers.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/v2/handlers.go#L1640

Added line #L1640 was not covered by tests
continue
}

Expand Down
19 changes: 5 additions & 14 deletions daemon/algod/api/server/v2/test/handlers_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,20 +181,11 @@ func (l *mockLedger) Block(rnd basics.Round) (blk bookkeeping.Block, err error)
return l.blocks[0], nil
}

func (l *mockLedger) AddressTxns(id basics.Address, r basics.Round) ([]transactions.SignedTxnWithAD, error) {
blk := l.blocks[r]

spec := transactions.SpecialAddresses{
FeeSink: blk.FeeSink,
RewardsPool: blk.RewardsPool,
}

var res []transactions.SignedTxnWithAD

for _, tx := range blk.Payset {
if tx.Txn.MatchAddress(id, spec) {
signedTxn := transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: tx.Txn}}
res = append(res, signedTxn)
func (l *mockLedger) TxnsFrom(id basics.Address, r basics.Round) ([]transactions.Transaction, error) {
var res []transactions.Transaction
for _, tx := range l.blocks[r].Payset {
if id == tx.Txn.Sender {
res = append(res, tx.Txn)
}
}
return res, nil
Expand Down
45 changes: 23 additions & 22 deletions daemon/algod/api/server/v2/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,16 @@
return nil
}

// Helper to convert basics.StateDelta -> *model.StateDelta
func stateDeltaToStateDelta(d basics.StateDelta) *model.StateDelta {
if len(d) == 0 {
// globalDeltaToStateDelta converts basics.StateDelta -> model.StateDelta. It
// should only be used on globals, because locals require extra context to
// translate account indexes.
func globalDeltaToStateDelta(bsd basics.StateDelta) model.StateDelta {
if len(bsd) == 0 {
return nil
}
var delta model.StateDelta
for k, v := range d {
delta = append(delta, model.EvalDeltaKeyValue{
msd := make(model.StateDelta, 0, len(bsd))
for k, v := range bsd {
msd = append(msd, model.EvalDeltaKeyValue{
Key: base64.StdEncoding.EncodeToString([]byte(k)),
Value: model.EvalDelta{
Action: uint64(v.Action),
Expand All @@ -304,7 +306,7 @@
},
})
}
return &delta
return msd
}

func edIndexToAddress(index uint64, txn *transactions.Transaction, shared []basics.Address) string {
Expand All @@ -321,23 +323,21 @@
}
}

func convertToDeltas(txn node.TxnWithStatus) (*[]model.AccountStateDelta, *model.StateDelta) {
var localStateDelta *[]model.AccountStateDelta
if len(txn.ApplyData.EvalDelta.LocalDeltas) > 0 {
d := make([]model.AccountStateDelta, 0)
shared := txn.ApplyData.EvalDelta.SharedAccts

for k, v := range txn.ApplyData.EvalDelta.LocalDeltas {
d = append(d, model.AccountStateDelta{
Address: edIndexToAddress(k, &txn.Txn.Txn, shared),
Delta: *(stateDeltaToStateDelta(v)),
})
}
func localDeltasToLocalDeltas(ed transactions.EvalDelta, txn *transactions.Transaction) []model.AccountStateDelta {
if len(ed.LocalDeltas) == 0 {
return nil
}
lsd := make([]model.AccountStateDelta, 0, len(ed.LocalDeltas))
shared := ed.SharedAccts

localStateDelta = &d
for k, v := range ed.LocalDeltas {
lsd = append(lsd, model.AccountStateDelta{
Address: edIndexToAddress(k, txn, shared),
Delta: globalDeltaToStateDelta(v),
})
}

return localStateDelta, stateDeltaToStateDelta(txn.ApplyData.EvalDelta.GlobalDelta)
return lsd
}

func convertLogs(txn node.TxnWithStatus) *[][]byte {
Expand Down Expand Up @@ -381,11 +381,12 @@
response.AssetIndex = omitEmpty(uint64(txn.ApplyData.ConfigAsset))
response.ApplicationIndex = omitEmpty(uint64(txn.ApplyData.ApplicationID))

response.LocalStateDelta = sliceOrNil(localDeltasToLocalDeltas(txn.ApplyData.EvalDelta, &txn.Txn))
response.GlobalStateDelta = sliceOrNil(globalDeltaToStateDelta(txn.ApplyData.EvalDelta.GlobalDelta))

Check warning on line 385 in daemon/algod/api/server/v2/utils.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/v2/utils.go#L384-L385

Added lines #L384 - L385 were not covered by tests
withStatus := node.TxnWithStatus{
Txn: txn.SignedTxn,
ApplyData: txn.ApplyData,
}
response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(withStatus)
response.Logs = convertLogs(withStatus)
response.Inners = convertInners(&withStatus)
return response
Expand Down
53 changes: 53 additions & 0 deletions data/bookkeeping/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,59 @@
}
)

// TxnDeadError defines an error type which indicates a transaction is outside of the
// round validity window.
type TxnDeadError struct {
Round basics.Round
FirstValid basics.Round
LastValid basics.Round
Early bool
}

func (err *TxnDeadError) Error() string {
return fmt.Sprintf("txn dead: round %d outside of %d--%d", err.Round, err.FirstValid, err.LastValid)

Check warning on line 275 in data/bookkeeping/block.go

View check run for this annotation

Codecov / codecov/patch

data/bookkeeping/block.go#L274-L275

Added lines #L274 - L275 were not covered by tests
}

// Alive checks to see if the transaction is still alive (can be applied) at the specified Round.
func (bh BlockHeader) Alive(tx transactions.Header) error {
// Check round validity
round := bh.Round
if round < tx.FirstValid || round > tx.LastValid {
return &TxnDeadError{
Round: round,
FirstValid: tx.FirstValid,
LastValid: tx.LastValid,
Early: round < tx.FirstValid,
}
}

// Check genesis ID
proto := config.Consensus[bh.CurrentProtocol]
genesisID := bh.GenesisID
if tx.GenesisID != "" && tx.GenesisID != genesisID {
return fmt.Errorf("tx.GenesisID <%s> does not match expected <%s>",
tx.GenesisID, genesisID)

Check warning on line 296 in data/bookkeeping/block.go

View check run for this annotation

Codecov / codecov/patch

data/bookkeeping/block.go#L295-L296

Added lines #L295 - L296 were not covered by tests
}

// Check genesis hash
if proto.SupportGenesisHash {
genesisHash := bh.GenesisHash
if tx.GenesisHash != (crypto.Digest{}) && tx.GenesisHash != genesisHash {
return fmt.Errorf("tx.GenesisHash <%s> does not match expected <%s>",
tx.GenesisHash, genesisHash)

Check warning on line 304 in data/bookkeeping/block.go

View check run for this annotation

Codecov / codecov/patch

data/bookkeeping/block.go#L303-L304

Added lines #L303 - L304 were not covered by tests
}
if proto.RequireGenesisHash && tx.GenesisHash == (crypto.Digest{}) {
return fmt.Errorf("required tx.GenesisHash is missing")

Check warning on line 307 in data/bookkeeping/block.go

View check run for this annotation

Codecov / codecov/patch

data/bookkeeping/block.go#L307

Added line #L307 was not covered by tests
}
} else {
if tx.GenesisHash != (crypto.Digest{}) {
return fmt.Errorf("tx.GenesisHash <%s> not allowed", tx.GenesisHash)

Check warning on line 311 in data/bookkeeping/block.go

View check run for this annotation

Codecov / codecov/patch

data/bookkeeping/block.go#L309-L311

Added lines #L309 - L311 were not covered by tests
}
}

return nil
}

// Hash returns the hash of a block header.
// The hash of a block is the hash of its header.
func (bh BlockHeader) Hash() BlockHash {
Expand Down
42 changes: 42 additions & 0 deletions data/bookkeeping/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1069,3 +1069,45 @@ func TestFirstYearsBonus(t *testing.T) {
// declined to about 72% (but foundation funding probably gone anyway)
a.InDelta(0.72, float64(bonus)/float64(plan.BaseAmount), 0.01)
}

func TestAlive(t *testing.T) {
partitiontest.PartitionTest(t)

bh := BlockHeader{
Round: 0,
GenesisHash: crypto.Digest{0x42},
}
bh.CurrentProtocol = protocol.ConsensusCurrentVersion

header := transactions.Header{
FirstValid: 5000,
LastValid: 5050,
GenesisID: bh.GenesisID,
GenesisHash: bh.GenesisHash,
}

bh.Round = header.FirstValid + 1
if err := bh.Alive(header); err != nil {
t.Errorf("transaction not alive during lifetime %v", err)
}

bh.Round = header.FirstValid
if err := bh.Alive(header); err != nil {
t.Errorf("transaction not alive at issuance %v", err)
}

bh.Round = header.LastValid
if err := bh.Alive(header); err != nil {
t.Errorf("transaction not alive at expiry %v", err)
}

bh.Round = header.FirstValid - 1
if bh.Alive(header) == nil {
t.Errorf("premature transaction alive %v", header)
}

bh.Round = header.LastValid + 1
if bh.Alive(header) == nil {
t.Errorf("expired transaction alive %v", header)
}
}
14 changes: 5 additions & 9 deletions data/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,25 +118,21 @@
return l, nil
}

// AddressTxns returns the list of transactions to/from a given address in specific round
func (l *Ledger) AddressTxns(id basics.Address, r basics.Round) ([]transactions.SignedTxnWithAD, error) {
// TxnsFrom returns the list of transactions sent by a given address in a round
func (l *Ledger) TxnsFrom(id basics.Address, r basics.Round) ([]transactions.Transaction, error) {

Check warning on line 122 in data/ledger.go

View check run for this annotation

Codecov / codecov/patch

data/ledger.go#L122

Added line #L122 was not covered by tests
blk, err := l.Block(r)
if err != nil {
return nil, err
}
spec := transactions.SpecialAddresses{
FeeSink: blk.FeeSink,
RewardsPool: blk.RewardsPool,
}

var res []transactions.SignedTxnWithAD
var res []transactions.Transaction

Check warning on line 128 in data/ledger.go

View check run for this annotation

Codecov / codecov/patch

data/ledger.go#L128

Added line #L128 was not covered by tests
payset, err := blk.DecodePaysetFlat()
if err != nil {
return nil, err
}
for _, tx := range payset {
if tx.Txn.MatchAddress(id, spec) {
res = append(res, tx)
if id == tx.Txn.Sender {
res = append(res, tx.Txn)

Check warning on line 135 in data/ledger.go

View check run for this annotation

Codecov / codecov/patch

data/ledger.go#L134-L135

Added lines #L134 - L135 were not covered by tests
}
}
return res, nil
Expand Down
Loading
Loading