@@ -140,8 +140,12 @@ func (cb *roundCowState) put(addr basics.Address, old basics.AccountData, new ba
 	}
 }
 
-func (cb *roundCowState) addTx(txn transactions.Transaction) {
-	cb.mods.Txids[txn.ID()] = txn.LastValid
+func (cb *roundCowState) addTx(txn transactions.Transaction, cachedTxid transactions.Txid) {
+	if cachedTxid == (transactions.Txid{}) {
+		cachedTxid = txn.ID()
+	}
+
+	cb.mods.Txids[cachedTxid] = txn.LastValid
 	cb.mods.txleases[txlease{sender: txn.Sender, lease: txn.Lease}] = txn.LastValid
 }
 
// Copyright (C) 2019-2020 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package ledger

import (
	"fmt"

	"github.com/algorand/go-algorand/config"
	"github.com/algorand/go-algorand/data/basics"
	"github.com/algorand/go-algorand/data/bookkeeping"
	"github.com/algorand/go-algorand/data/transactions"
)

//   ___________________
// < cow = Copy On Write >
//   -------------------
//          \   ^__^
//           \  (oo)\_______
//              (__)\       )\/\
//                  ||----w |
//                  ||     ||

type roundCowParent interface {
	lookup(basics.Address) (basics.AccountData, error)
	isDup(basics.Round, basics.Round, transactions.Txid, txlease) (bool, error)
	txnCounter() uint64
	getAssetCreator(assetIdx basics.AssetIndex) (basics.Address, error)
}

type roundCowState struct {
	lookupParent roundCowParent
	commitParent *roundCowState
	proto        config.ConsensusParams
	mods         StateDelta
}

// StateDelta describes the delta between a given round to the previous round
type StateDelta struct {
	// modified accounts
	accts map[basics.Address]accountDelta

	// new Txids for the txtail and TxnCounter, mapped to txn.LastValid
	Txids map[transactions.Txid]basics.Round

	// new txleases for the txtail mapped to expiration
	txleases map[txlease]basics.Round

	// new assets creator lookup table
	assets map[basics.AssetIndex]modifiedAsset

	// new block header; read-only
	hdr *bookkeeping.BlockHeader
}

func makeRoundCowState(b roundCowParent, hdr bookkeeping.BlockHeader) *roundCowState {
	return &roundCowState{
		lookupParent: b,
		commitParent: nil,
		proto:        config.Consensus[hdr.CurrentProtocol],
		mods: StateDelta{
			accts:    make(map[basics.Address]accountDelta),
			Txids:    make(map[transactions.Txid]basics.Round),
			txleases: make(map[txlease]basics.Round),
			assets:   make(map[basics.AssetIndex]modifiedAsset),
			hdr:      &hdr,
		},
	}
}

func (cb *roundCowState) rewardsLevel() uint64 {
	return cb.mods.hdr.RewardsLevel
}

func (cb *roundCowState) getAssetCreator(aidx basics.AssetIndex) (basics.Address, error) {
	delta, ok := cb.mods.assets[aidx]
	if ok {
		if delta.created {
			return delta.creator, nil
		}
		return basics.Address{}, fmt.Errorf("asset %d has been deleted", aidx)
	}
	return cb.lookupParent.getAssetCreator(aidx)
}

func (cb *roundCowState) lookup(addr basics.Address) (data basics.AccountData, err error) {
	d, ok := cb.mods.accts[addr]
	if ok {
		return d.new, nil
	}

	return cb.lookupParent.lookup(addr)
}

func (cb *roundCowState) isDup(firstValid, lastValid basics.Round, txid transactions.Txid, txl txlease) (bool, error) {
	_, present := cb.mods.Txids[txid]
	if present {
		return true, nil
	}

	if cb.proto.SupportTransactionLeases && (txl.lease != [32]byte{}) {
		expires, ok := cb.mods.txleases[txl]
		if ok && cb.mods.hdr.Round <= expires {
			return true, nil
		}
	}

	return cb.lookupParent.isDup(firstValid, lastValid, txid, txl)
}

func (cb *roundCowState) txnCounter() uint64 {
	return cb.lookupParent.txnCounter() + uint64(len(cb.mods.Txids))
}

func (cb *roundCowState) put(addr basics.Address, old basics.AccountData, new basics.AccountData) {
	prev, present := cb.mods.accts[addr]
	if present {
		cb.mods.accts[addr] = accountDelta{old: prev.old, new: new}
	} else {
		cb.mods.accts[addr] = accountDelta{old: old, new: new}
	}

	// Get which asset indices were created and deleted, and update state
	assetDeltas := getChangedAssetIndices(addr, accountDelta{old: old, new: new})
	for aidx, delta := range assetDeltas {
		cb.mods.assets[aidx] = delta
	}
}

func (cb *roundCowState) addTx(txn transactions.Transaction) {
	cb.mods.Txids[txn.ID()] = txn.LastValid
	cb.mods.txleases[txlease{sender: txn.Sender, lease: txn.Lease}] = txn.LastValid
}

func (cb *roundCowState) child() *roundCowState {
	return &roundCowState{
		lookupParent: cb,
		commitParent: cb,
		proto:        cb.proto,
		mods: StateDelta{
			accts:    make(map[basics.Address]accountDelta),
			Txids:    make(map[transactions.Txid]basics.Round),
			txleases: make(map[txlease]basics.Round),
			assets:   make(map[basics.AssetIndex]modifiedAsset),
			hdr:      cb.mods.hdr,
		},
	}
}

func (cb *roundCowState) commitToParent() {
	for addr, delta := range cb.mods.accts {
		prev, present := cb.commitParent.mods.accts[addr]
		if present {
			cb.commitParent.mods.accts[addr] = accountDelta{
				old: prev.old,
				new: delta.new,
			}
		} else {
			cb.commitParent.mods.accts[addr] = delta
		}
	}

	for txid, lv := range cb.mods.Txids {
		cb.commitParent.mods.Txids[txid] = lv
	}
	for txl, expires := range cb.mods.txleases {
		cb.commitParent.mods.txleases[txl] = expires
	}
	for aidx, delta := range cb.mods.assets {
		cb.commitParent.mods.assets[aidx] = delta
	}
}

func (cb *roundCowState) modifiedAccounts() []basics.Address {
	res := make([]basics.Address, len(cb.mods.accts))
	i := 0
	for addr := range cb.mods.accts {
		res[i] = addr
		i++
	}
	return res
}

nit: you could eliminate this conditional check if you were to add the `cachedTxid = txn.ID()` on the `if !eval.validate` condition in the BlockEvaluator::transaction.