summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Lee <64482439+algojohnlee@users.noreply.github.com>2023-12-19 13:38:03 -0500
committerGitHub <noreply@github.com>2023-12-19 13:38:03 -0500
commit9c4f70771de79de9681380088cbe317e3bd37f88 (patch)
tree9d82aff9ddd8c4eac2aab1ef0fbeca492c5bf67f
parent14c0d8d0811e8a83a0f55f6692e233873b80199b (diff)
parentb2c158d8d9357d3cd100c2c39f763c819ebb4526 (diff)
Merge pull request #5862 from Algo-devops-service/relbeta3.21.0v3.21.0-beta
-rw-r--r--.aptly.conf2
-rw-r--r--agreement/actions.go3
-rw-r--r--agreement/dynamicFilterTimeoutParams.go2
-rw-r--r--agreement/player.go23
-rw-r--r--agreement/player_permutation_test.go9
-rw-r--r--agreement/player_test.go32
-rw-r--r--agreement/router.go9
-rw-r--r--agreement/service_test.go44
-rw-r--r--agreement/types.go22
-rw-r--r--buildnumber.dat2
-rw-r--r--catchup/service.go16
-rw-r--r--catchup/service_test.go24
-rw-r--r--cmd/tealdbg/localLedger.go4
-rw-r--r--config/consensus.go41
-rw-r--r--config/localTemplate.go6
-rw-r--r--config/version.go2
-rw-r--r--daemon/algod/api/algod.oas2.json8
-rw-r--r--daemon/algod/api/algod.oas3.yml8
-rw-r--r--daemon/algod/api/server/common/handlers.go4
-rw-r--r--daemon/algod/api/server/v2/dryrun.go5
-rw-r--r--daemon/algod/api/server/v2/generated/data/routes.go56
-rw-r--r--daemon/algod/api/server/v2/generated/experimental/routes.go410
-rw-r--r--daemon/algod/api/server/v2/generated/model/types.go6
-rw-r--r--daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go416
-rw-r--r--daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go210
-rw-r--r--daemon/algod/api/server/v2/generated/participating/private/routes.go422
-rw-r--r--daemon/algod/api/server/v2/generated/participating/public/routes.go80
-rw-r--r--daemon/algod/api/server/v2/utils.go14
-rw-r--r--data/bookkeeping/block_test.go4
-rw-r--r--data/transactions/logic/README.md11
-rw-r--r--data/transactions/logic/README_in.md6
-rw-r--r--data/transactions/logic/TEAL_opcodes_v10.md21
-rw-r--r--data/transactions/logic/TEAL_opcodes_v8.md2
-rw-r--r--data/transactions/logic/TEAL_opcodes_v9.md2
-rw-r--r--data/transactions/logic/assembler_test.go58
-rw-r--r--data/transactions/logic/box.go110
-rw-r--r--data/transactions/logic/box_test.go100
-rw-r--r--data/transactions/logic/debugger.go2
-rw-r--r--data/transactions/logic/doc.go6
-rw-r--r--data/transactions/logic/eval.go27
-rw-r--r--data/transactions/logic/evalAppTxn_test.go31
-rw-r--r--data/transactions/logic/evalStateful_test.go10
-rw-r--r--data/transactions/logic/eval_test.go37
-rw-r--r--data/transactions/logic/fields.go5
-rw-r--r--data/transactions/logic/fields_string.go7
-rw-r--r--data/transactions/logic/fields_test.go58
-rw-r--r--data/transactions/logic/langspec_v1.json2
-rw-r--r--data/transactions/logic/langspec_v10.json43
-rw-r--r--data/transactions/logic/langspec_v2.json2
-rw-r--r--data/transactions/logic/langspec_v3.json2
-rw-r--r--data/transactions/logic/langspec_v4.json2
-rw-r--r--data/transactions/logic/langspec_v5.json2
-rw-r--r--data/transactions/logic/langspec_v6.json2
-rw-r--r--data/transactions/logic/langspec_v7.json2
-rw-r--r--data/transactions/logic/langspec_v8.json4
-rw-r--r--data/transactions/logic/langspec_v9.json4
-rw-r--r--data/transactions/logic/ledger_test.go8
-rw-r--r--data/transactions/logic/mocktracer/scenarios.go28
-rw-r--r--data/transactions/logic/mocktracer/tracer.go32
-rw-r--r--data/transactions/logic/opcodeExplain.go12
-rw-r--r--data/transactions/logic/opcodes.go5
-rw-r--r--data/transactions/logic/teal.tmLanguage.json4
-rw-r--r--data/transactions/logic/tracer.go4
-rw-r--r--data/transactions/logic/tracer_test.go8
-rw-r--r--data/transactions/verify/txn_test.go19
-rw-r--r--docker/build/aptly.Dockerfile24
-rw-r--r--docker/build/docker.ubuntu.Dockerfile14
-rw-r--r--docs/participation_key_lifecycle.md210
-rw-r--r--ledger/eval/appcow_test.go5
-rw-r--r--ledger/eval/cow.go6
-rw-r--r--ledger/eval/cow_test.go5
-rw-r--r--ledger/eval/eval.go5
-rw-r--r--ledger/eval/eval_test.go13
-rw-r--r--ledger/simulation/simulation_eval_test.go644
-rw-r--r--ledger/simulation/simulator_test.go4
-rw-r--r--ledger/simulation/trace.go10
-rw-r--r--ledger/simulation/tracer.go28
-rw-r--r--ledger/testing/consensusRange.go1
-rw-r--r--ledger/testing/consensusRange_test.go2
-rw-r--r--network/requestTracker.go109
-rw-r--r--network/requestTracker_test.go127
-rw-r--r--network/websocketProxy_test.go322
-rw-r--r--network/wsNetwork.go26
-rw-r--r--network/wsNetwork_test.go151
-rw-r--r--network/wsPeer.go12
-rw-r--r--network/wsPeer_test.go6
-rw-r--r--package-deploy.yaml5
-rw-r--r--protocol/consensus.go8
-rwxr-xr-xscripts/release/mule/deploy/deb/deploy.sh32
-rw-r--r--test/e2e-go/features/stateproofs/stateproofs_test.go6
-rw-r--r--test/framework/fixtures/libgoalFixture.go4
91 files changed, 2950 insertions, 1391 deletions
diff --git a/.aptly.conf b/.aptly.conf
index 6300abc3b..8badff78d 100644
--- a/.aptly.conf
+++ b/.aptly.conf
@@ -1,5 +1,4 @@
{
- "rootDir": "/root/aptly",
"downloadConcurrency": 4,
"downloadSpeedLimit": 0,
"architectures": [],
@@ -27,4 +26,3 @@
},
"SwiftPublishEndpoints": {}
}
-
diff --git a/agreement/actions.go b/agreement/actions.go
index 0e0d5f19c..ef2dd76c2 100644
--- a/agreement/actions.go
+++ b/agreement/actions.go
@@ -232,8 +232,7 @@ type ensureAction struct {
Payload proposal
// the certificate proving commitment
Certificate Certificate
-
- // The time that the winning proposal-vote was validated, relative to the beginning of the round
+ // The time that the winning proposal-vote was validated for round credentialRoundLag back from the current one
voteValidatedAt time.Duration
// The dynamic filter timeout calculated for this round, even if not enabled, for reporting to telemetry.
dynamicFilterTimeout time.Duration
diff --git a/agreement/dynamicFilterTimeoutParams.go b/agreement/dynamicFilterTimeoutParams.go
index 36348615b..8b02bf082 100644
--- a/agreement/dynamicFilterTimeoutParams.go
+++ b/agreement/dynamicFilterTimeoutParams.go
@@ -31,7 +31,7 @@ const dynamicFilterCredentialArrivalHistory int = 40
// DynamicFilterTimeoutLowerBound specifies a minimal duration that the
// filter timeout must meet.
-const dynamicFilterTimeoutLowerBound time.Duration = 500 * time.Millisecond
+const dynamicFilterTimeoutLowerBound time.Duration = 2500 * time.Millisecond
// DynamicFilterTimeoutCredentialArrivalHistoryIdx specified which sample to use
// out of a sorted DynamicFilterCredentialArrivalHistory-sized array of time
diff --git a/agreement/player.go b/agreement/player.go
index 21d65ecf8..4e7ec685e 100644
--- a/agreement/player.go
+++ b/agreement/player.go
@@ -100,10 +100,19 @@ func (p *player) handle(r routerHandle, e event) []action {
r.t.logTimeout(*p)
}
+ var deadlineTimeout time.Duration
+ if e.Proto.Version == "" || e.Proto.Err != nil {
+ r.t.log.Errorf("failed to read valid protocol version for timeout event (proto %v): %v. "+
+ "Falling Back to default deadline timeout.", e.Proto.Version, e.Proto.Err)
+ deadlineTimeout = DefaultDeadlineTimeout()
+ } else {
+ deadlineTimeout = DeadlineTimeout(p.Period, e.Proto.Version)
+ }
+
switch p.Step {
case soft:
// precondition: nap = false
- actions = p.issueSoftVote(r)
+ actions = p.issueSoftVote(r, deadlineTimeout)
p.Step = cert
// update tracer state to match player
r.t.setMetadata(tracerMetadata{p.Round, p.Period, p.Step})
@@ -113,16 +122,16 @@ func (p *player) handle(r routerHandle, e event) []action {
p.Step = next
// update tracer state to match player
r.t.setMetadata(tracerMetadata{p.Round, p.Period, p.Step})
- return p.issueNextVote(r)
+ return p.issueNextVote(r, deadlineTimeout)
default:
if p.Napping {
- return p.issueNextVote(r) // sets p.Napping to false
+ return p.issueNextVote(r, deadlineTimeout) // sets p.Napping to false
}
// not napping, so we should enter a new step
p.Step++ // note: this must happen before next timeout setting.
// TODO add unit test to ensure that deadlines increase monotonically here
- lower, upper := p.Step.nextVoteRanges()
+ lower, upper := p.Step.nextVoteRanges(deadlineTimeout)
delta := time.Duration(e.RandomEntropy % uint64(upper-lower))
p.Napping = true
@@ -158,7 +167,7 @@ func (p *player) handleFastTimeout(r routerHandle, e timeoutEvent) []action {
return p.issueFastVote(r)
}
-func (p *player) issueSoftVote(r routerHandle) (actions []action) {
+func (p *player) issueSoftVote(r routerHandle, deadlineTimeout time.Duration) (actions []action) {
defer func() {
p.Deadline = Deadline{Duration: deadlineTimeout, Type: TimeoutDeadline}
}()
@@ -202,7 +211,7 @@ func (p *player) issueCertVote(r routerHandle, e committableEvent) action {
return pseudonodeAction{T: attest, Round: p.Round, Period: p.Period, Step: cert, Proposal: e.Proposal}
}
-func (p *player) issueNextVote(r routerHandle) []action {
+func (p *player) issueNextVote(r routerHandle, deadlineTimeout time.Duration) []action {
actions := p.partitionPolicy(r)
a := pseudonodeAction{T: attest, Round: p.Round, Period: p.Period, Step: p.Step, Proposal: bottom}
@@ -226,7 +235,7 @@ func (p *player) issueNextVote(r routerHandle) []action {
r.t.timeR().RecStep(p.Period, p.Step, a.Proposal)
- _, upper := p.Step.nextVoteRanges()
+ _, upper := p.Step.nextVoteRanges(deadlineTimeout)
p.Napping = false
p.Deadline = Deadline{Duration: upper, Type: TimeoutDeadline}
return actions
diff --git a/agreement/player_permutation_test.go b/agreement/player_permutation_test.go
index bd2c2c84d..2c598ff77 100644
--- a/agreement/player_permutation_test.go
+++ b/agreement/player_permutation_test.go
@@ -811,17 +811,16 @@ func TestPlayerPermutation(t *testing.T) {
}
func playerPermutationCheck(t *testing.T, enableDynamicFilterTimeout bool) {
- // create a protocol version where dynamic filter is enabled
- version, _, configCleanup := createDynamicFilterConfig()
+ // create a protocol where dynamic filter is set based on the enableDynamicFilterTimeout flag
+ dynamicFilterOverriddenProtocol, _, configCleanup := overrideConfigWithDynamicFilterParam(enableDynamicFilterTimeout)
defer configCleanup()
for i := 0; i < 7; i++ {
for j := 0; j < 14; j++ {
_, pMachine, helper := getPlayerPermutation(t, i)
inMsg := getMessageEventPermutation(t, j, helper)
- if enableDynamicFilterTimeout {
- inMsg.Proto = ConsensusVersionView{Version: version}
- }
+ inMsg.Proto = ConsensusVersionView{Version: dynamicFilterOverriddenProtocol}
+
err, panicErr := pMachine.transition(inMsg)
fmt.Println(pMachine.getTrace().events)
fmt.Println("")
diff --git a/agreement/player_test.go b/agreement/player_test.go
index 2bb2ffe81..a60c9f44c 100644
--- a/agreement/player_test.go
+++ b/agreement/player_test.go
@@ -37,7 +37,7 @@ func init() {
}
func makeTimeoutEvent() timeoutEvent {
- return timeoutEvent{T: timeout, RandomEntropy: crypto.RandUint64()}
+ return timeoutEvent{T: timeout, RandomEntropy: crypto.RandUint64(), Proto: ConsensusVersionView{Version: protocol.ConsensusCurrentVersion}}
}
func generateProposalEvents(t *testing.T, player player, accs testAccountData, f testBlockFactory, ledger Ledger) (voteBatch []event, payloadBatch []event, lowestProposal proposalValue) {
@@ -3240,7 +3240,7 @@ func TestPlayerAlwaysResynchsPinnedValue(t *testing.T) {
func TestPlayerRetainsReceivedValidatedAtOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(131)
pWhite, pM, helper := setupP(t, r-1, p, soft)
@@ -3262,7 +3262,7 @@ func TestPlayerRetainsReceivedValidatedAtOneSample(t *testing.T) {
func TestPlayerRetainsReceivedValidatedAtCredentialHistory(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-credentialRoundLag-1, p, soft)
@@ -3301,7 +3301,7 @@ func TestPlayerRetainsReceivedValidatedAtCredentialHistory(t *testing.T) {
func TestPlayerRetainsEarlyReceivedValidatedAtOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-1, p, soft)
@@ -3339,7 +3339,7 @@ func testClockForRound(t *testing.T, pWhite *player, fixedDur time.Duration, cur
func TestPlayerRetainsLateReceivedValidatedAtOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-1, p, soft)
@@ -3382,7 +3382,7 @@ func TestPlayerRetainsReceivedValidatedAtForHistoryWindowLateBetter(t *testing.T
}
func testPlayerRetainsReceivedValidatedAtForHistoryWindow(t *testing.T, addBetterLate bool) {
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-1, p, soft)
@@ -3449,7 +3449,7 @@ func testPlayerRetainsReceivedValidatedAtForHistoryWindow(t *testing.T, addBette
func TestPlayerRetainsReceivedValidatedAtPPOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version, _, configCleanup := createDynamicFilterConfig()
+ version, _, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
const r = round(20239)
const p = period(0)
@@ -3505,7 +3505,7 @@ func TestPlayerRetainsReceivedValidatedAtPPOneSample(t *testing.T) {
func TestPlayerRetainsEarlyReceivedValidatedAtPPOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version, _, configCleanup := createDynamicFilterConfig()
+ version, _, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
const r = round(20239)
@@ -3559,7 +3559,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtPPOneSample(t *testing.T) {
func TestPlayerRetainsLateReceivedValidatedAtPPOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version, _, configCleanup := createDynamicFilterConfig()
+ version, _, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
const r = round(20239)
const p = period(0)
@@ -3613,7 +3613,7 @@ func TestPlayerRetainsLateReceivedValidatedAtPPOneSample(t *testing.T) {
func TestPlayerRetainsReceivedValidatedAtPPForHistoryWindow(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-1, p, soft)
@@ -3655,7 +3655,7 @@ func TestPlayerRetainsReceivedValidatedAtAVPPOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
// create a protocol version where dynamic lambda is enabled
- version, _, configCleanup := createDynamicFilterConfig()
+ version, _, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
const r = round(20239)
const p = period(0)
@@ -3710,7 +3710,7 @@ func TestPlayerRetainsReceivedValidatedAtAVPPOneSample(t *testing.T) {
func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-1, p, soft)
@@ -3729,7 +3729,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) {
require.Equal(t, pWhite.lowestCredentialArrivals.writePtr, 0)
// create a protocol version where dynamic filter is enabled
- version, _, configCleanup := createDynamicFilterConfig()
+ version, _, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
// send votePresent message (mimicking the first AV message validating)
@@ -3767,7 +3767,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) {
func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-1, p, soft)
@@ -3786,7 +3786,7 @@ func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) {
require.Equal(t, pWhite.lowestCredentialArrivals.writePtr, 0)
// create a protocol version where dynamic filter is enabled
- version, _, configCleanup := createDynamicFilterConfig()
+ version, _, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
// send votePresent message (mimicking the first AV message validating)
@@ -3821,7 +3821,7 @@ func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) {
func TestPlayerRetainsReceivedValidatedAtAVPPHistoryWindow(t *testing.T) {
partitiontest.PartitionTest(t)
- version := protocol.ConsensusFuture
+ version := protocol.ConsensusCurrentVersion
const r = round(20239)
const p = period(0)
pWhite, pM, helper := setupP(t, r-1, p, soft)
diff --git a/agreement/router.go b/agreement/router.go
index 6ab144470..86b9a1877 100644
--- a/agreement/router.go
+++ b/agreement/router.go
@@ -59,7 +59,16 @@ var credentialRoundLag round
func init() {
// credential arrival time should be at most 2*config.Protocol.SmallLambda after it was sent
+ // Note that the credentialRoundLag is inversely proportional to the dynamicFilterTimeoutLowerBound
+ // in the default formula. Since we are adjusting this lower bound over time,
+ // for consistency in analytics we are setting the minimum to be 8 rounds
+ // (equivalent to a dynamicFilterTimeoutLowerBound of 500 ms).
+ minCredentialRoundLag := round(8) // round 2*2000ms / 500ms
credentialRoundLag = round(2 * config.Protocol.SmallLambda / dynamicFilterTimeoutLowerBound)
+
+ if credentialRoundLag < minCredentialRoundLag {
+ credentialRoundLag = minCredentialRoundLag
+ }
if credentialRoundLag*round(dynamicFilterTimeoutLowerBound) < round(2*config.Protocol.SmallLambda) {
credentialRoundLag++
}
diff --git a/agreement/service_test.go b/agreement/service_test.go
index f6ff27fd2..0a7ef55f9 100644
--- a/agreement/service_test.go
+++ b/agreement/service_test.go
@@ -1050,10 +1050,10 @@ func TestAgreementHistoricalClocksCleanup(t *testing.T) {
simulateAgreement(t, 5, int(credentialRoundLag)+10, disabled)
}
-func createDynamicFilterConfig() (version protocol.ConsensusVersion, consensusVersion func(r basics.Round) (protocol.ConsensusVersion, error), configCleanup func()) {
+func overrideConfigWithDynamicFilterParam(dynamicFilterTimeoutEnabled bool) (version protocol.ConsensusVersion, consensusVersion func(r basics.Round) (protocol.ConsensusVersion, error), configCleanup func()) {
version = protocol.ConsensusVersion("test-protocol-filtertimeout")
protoParams := config.Consensus[protocol.ConsensusCurrentVersion]
- protoParams.DynamicFilterTimeout = true
+ protoParams.DynamicFilterTimeout = dynamicFilterTimeoutEnabled
config.Consensus[version] = protoParams
consensusVersion = func(r basics.Round) (protocol.ConsensusVersion, error) {
@@ -1074,7 +1074,7 @@ func TestAgreementSynchronousFuture5_DynamicFilterRounds(t *testing.T) {
t.Skip("Skipping agreement integration test")
}
- _, consensusVersion, configCleanup := createDynamicFilterConfig()
+ _, consensusVersion, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
if dynamicFilterCredentialArrivalHistory <= 0 {
@@ -1105,7 +1105,7 @@ func TestDynamicFilterTimeoutResets(t *testing.T) {
t.Skip("Skipping agreement integration test")
}
- version, consensusVersion, configCleanup := createDynamicFilterConfig()
+ version, consensusVersion, configCleanup := overrideConfigWithDynamicFilterParam(true)
defer configCleanup()
if dynamicFilterCredentialArrivalHistory <= 0 {
@@ -1334,7 +1334,7 @@ func TestAgreementFastRecoveryDownMiss(t *testing.T) {
triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer
@@ -1435,7 +1435,7 @@ func TestAgreementFastRecoveryLate(t *testing.T) {
}
}
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer
@@ -1548,7 +1548,7 @@ func TestAgreementFastRecoveryRedo(t *testing.T) {
}
}
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer
@@ -1589,7 +1589,7 @@ func TestAgreementFastRecoveryRedo(t *testing.T) {
triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer
@@ -1681,7 +1681,7 @@ func TestAgreementBlockReplayBug_b29ea57(t *testing.T) {
triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
}
@@ -1690,7 +1690,7 @@ func TestAgreementBlockReplayBug_b29ea57(t *testing.T) {
triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
}
@@ -1743,7 +1743,7 @@ func TestAgreementLateCertBug(t *testing.T) {
closeFn()
baseNetwork.repairAll()
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
}
@@ -1819,7 +1819,7 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) {
}
}
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
require.Equal(t, 4, int(zeroes))
}
@@ -1846,7 +1846,7 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) {
}
}
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
require.Equal(t, 5, int(zeroes))
}
@@ -1924,7 +1924,7 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) {
}
return params
})
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
require.Equal(t, 4, int(zeroes))
}
@@ -1950,7 +1950,7 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) {
panic(errstr)
}
}
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
}
@@ -2025,7 +2025,7 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) {
}
// generate a bottom quorum; let only one node see it.
baseNetwork.crown(0)
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
if clocks[0].(*testingClock).zeroes != zeroes+1 {
errstr := fmt.Sprintf("node 0 did not enter new period from bot quorum")
panic(errstr)
@@ -2043,11 +2043,11 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) {
activityMonitor.waitForQuiet()
// actually create the value quorum
- _, upper := (next).nextVoteRanges()
+ _, upper := (next).nextVoteRanges(DeadlineTimeout(0, version))
triggerGlobalTimeout(upper, TimeoutDeadline, clocks[1:], activityMonitor) // activates next timers
zeroes = expectNoNewPeriod(clocks[1:], zeroes)
- lower, upper := (next + 1).nextVoteRanges()
+ lower, upper := (next + 1).nextVoteRanges(DeadlineTimeout(0, version))
delta := time.Duration(testingRand{}.Uint64() % uint64(upper-lower))
triggerGlobalTimeout(lower+delta, TimeoutDeadline, clocks[1:], activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
@@ -2076,7 +2076,7 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) {
}
}
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
}
@@ -2190,7 +2190,7 @@ func TestAgreementSlowPayloadsPostDeadline(t *testing.T) {
{
triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor)
zeroes = expectNoNewPeriod(clocks, zeroes)
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
}
@@ -2251,7 +2251,7 @@ func TestAgreementLargePeriods(t *testing.T) {
zeroes = expectNoNewPeriod(clocks, zeroes)
baseNetwork.repairAll()
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(period(p), version), TimeoutDeadline, clocks, activityMonitor)
zeroes = expectNewPeriod(clocks, zeroes)
require.Equal(t, 4+p, int(zeroes))
}
@@ -2363,7 +2363,7 @@ func TestAgreementRegression_WrongPeriodPayloadVerificationCancellation_8ba23942
// release proposed blocks in a controlled manner to prevent oversubscription of verification
pocket1 := make(chan multicastParams, 100)
closeFn = baseNetwork.pocketAllCompound(pocket1)
- triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor)
+ triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor)
baseNetwork.repairAll()
close(pocket1)
{
diff --git a/agreement/types.go b/agreement/types.go
index 664fd3a2b..000f03e1e 100644
--- a/agreement/types.go
+++ b/agreement/types.go
@@ -50,7 +50,7 @@ type Deadline struct {
Type TimeoutType
}
-var deadlineTimeout = config.Protocol.BigLambda + config.Protocol.SmallLambda
+var defaultDeadlineTimeout = config.Protocol.BigLambda + config.Protocol.SmallLambda
var partitionStep = next + 3
var recoveryExtraTimeout = config.Protocol.SmallLambda
@@ -63,9 +63,17 @@ func FilterTimeout(p period, v protocol.ConsensusVersion) time.Duration {
return config.Consensus[v].AgreementFilterTimeout
}
-// DeadlineTimeout is the duration of the second agreement step.
-func DeadlineTimeout() time.Duration {
- return deadlineTimeout
+// DeadlineTimeout is the duration of the second agreement step, varying based on period and consensus version.
+func DeadlineTimeout(p period, v protocol.ConsensusVersion) time.Duration {
+ if p == 0 {
+ return config.Consensus[v].AgreementDeadlineTimeoutPeriod0
+ }
+ return defaultDeadlineTimeout
+}
+
+// DefaultDeadlineTimeout is the default duration of the second agreement step.
+func DefaultDeadlineTimeout() time.Duration {
+ return defaultDeadlineTimeout
}
type (
@@ -92,10 +100,10 @@ const (
down
)
-func (s step) nextVoteRanges() (lower, upper time.Duration) {
+func (s step) nextVoteRanges(deadlineTimeout time.Duration) (lower, upper time.Duration) {
extra := recoveryExtraTimeout // eg 2000 ms
- lower = deadlineTimeout // eg 17000 ms (15000 + 2000)
- upper = lower + extra // eg 19000 ms
+ lower = deadlineTimeout // based on types.DeadlineTimeout()
+ upper = lower + extra
for i := next; i < s; i++ {
extra *= 2
diff --git a/buildnumber.dat b/buildnumber.dat
index d00491fd7..573541ac9 100644
--- a/buildnumber.dat
+++ b/buildnumber.dat
@@ -1 +1 @@
-1
+0
diff --git a/catchup/service.go b/catchup/service.go
index bcf204b13..01ff1678e 100644
--- a/catchup/service.go
+++ b/catchup/service.go
@@ -91,7 +91,7 @@ type Service struct {
net network.GossipNode
auth BlockAuthenticator
parallelBlocks uint64
- deadlineTimeout time.Duration
+ roundTimeEstimate time.Duration
prevBlockFetchTime time.Time
blockValidationPool execpool.BacklogPool
@@ -146,7 +146,7 @@ func MakeService(log logging.Logger, config config.Local, net network.GossipNode
s.unmatchedPendingCertificates = unmatchedPendingCertificates
s.log = log.With("Context", "sync")
s.parallelBlocks = config.CatchupParallelBlocks
- s.deadlineTimeout = agreement.DeadlineTimeout()
+ s.roundTimeEstimate = agreement.DefaultDeadlineTimeout()
s.blockValidationPool = blockValidationPool
s.syncNow = make(chan struct{}, 1)
@@ -556,11 +556,11 @@ func (s *Service) pipelinedFetch(seedLookback uint64) {
// if ledger is busy, pause for some time to let the fetchAndWrite goroutines to finish fetching in-flight blocks.
start := time.Now()
- for (s.ledger.IsWritingCatchpointDataFile() || s.ledger.IsBehindCommittingDeltas()) && time.Since(start) < s.deadlineTimeout {
+ for (s.ledger.IsWritingCatchpointDataFile() || s.ledger.IsBehindCommittingDeltas()) && time.Since(start) < s.roundTimeEstimate {
time.Sleep(100 * time.Millisecond)
}
- // if ledger is still busy after s.deadlineTimeout timeout then abort the current pipelinedFetch invocation.
+ // if ledger is still busy after s.roundTimeEstimate timeout then abort the current pipelinedFetch invocation.
// if we're writing a catchpoint file, stop catching up to reduce the memory pressure. Once we finish writing the file we
// could resume with the catchup.
@@ -616,7 +616,7 @@ func (s *Service) periodicSync() {
s.sync()
}
stuckInARow := 0
- sleepDuration := s.deadlineTimeout
+ sleepDuration := s.roundTimeEstimate
for {
currBlock := s.ledger.LastRound()
select {
@@ -627,7 +627,7 @@ func (s *Service) periodicSync() {
stuckInARow = 0
// go to sleep for a short while, for a random duration.
// we want to sleep for a random duration since it would "de-syncronize" us from the ledger advance sync
- sleepDuration = time.Duration(crypto.RandUint63()) % s.deadlineTimeout
+ sleepDuration = time.Duration(crypto.RandUint63()) % s.roundTimeEstimate
continue
case <-s.syncNow:
if s.parallelBlocks == 0 || s.ledger.IsWritingCatchpointDataFile() || s.ledger.IsBehindCommittingDeltas() {
@@ -637,8 +637,8 @@ func (s *Service) periodicSync() {
s.log.Info("Immediate resync triggered; resyncing")
s.sync()
case <-time.After(sleepDuration):
- if sleepDuration < s.deadlineTimeout || s.cfg.DisableNetworking {
- sleepDuration = s.deadlineTimeout
+ if sleepDuration < s.roundTimeEstimate || s.cfg.DisableNetworking {
+ sleepDuration = s.roundTimeEstimate
continue
}
// if the catchup is disabled in the config file, just skip it.
diff --git a/catchup/service_test.go b/catchup/service_test.go
index fc0ae38e1..c712fab5a 100644
--- a/catchup/service_test.go
+++ b/catchup/service_test.go
@@ -237,7 +237,7 @@ func TestSyncRound(t *testing.T) {
localCfg := config.GetDefaultLocal()
s := MakeService(logging.Base(), localCfg, net, local, auth, nil, nil)
s.log = &periodicSyncLogger{Logger: logging.Base()}
- s.deadlineTimeout = 2 * time.Second
+ s.roundTimeEstimate = 2 * time.Second
// Set disable round success
err = s.SetDisableSyncRound(3)
@@ -246,14 +246,14 @@ func TestSyncRound(t *testing.T) {
s.Start()
defer s.Stop()
// wait past the initial sync - which is known to fail due to the above "auth"
- time.Sleep(s.deadlineTimeout*2 - 200*time.Millisecond)
+ time.Sleep(s.roundTimeEstimate*2 - 200*time.Millisecond)
require.Equal(t, initialLocalRound, local.LastRound())
auth.alter(-1, false)
// wait until the catchup is done. Since we've might have missed the sleep window, we need to wait
// until the synchronization is complete.
waitStart := time.Now()
- for time.Since(waitStart) < 2*s.deadlineTimeout {
+ for time.Since(waitStart) < 2*s.roundTimeEstimate {
if remote.LastRound() == local.LastRound() {
break
}
@@ -276,7 +276,7 @@ func TestSyncRound(t *testing.T) {
s.UnsetDisableSyncRound()
// wait until the catchup is done
waitStart = time.Now()
- for time.Since(waitStart) < 8*s.deadlineTimeout {
+ for time.Since(waitStart) < 8*s.roundTimeEstimate {
if remote.LastRound() == local.LastRound() {
break
}
@@ -326,19 +326,19 @@ func TestPeriodicSync(t *testing.T) {
// Make Service
s := MakeService(logging.Base(), defaultConfig, net, local, auth, nil, nil)
s.log = &periodicSyncLogger{Logger: logging.Base()}
- s.deadlineTimeout = 2 * time.Second
+ s.roundTimeEstimate = 2 * time.Second
s.Start()
defer s.Stop()
// wait past the initial sync - which is known to fail due to the above "auth"
- time.Sleep(s.deadlineTimeout*2 - 200*time.Millisecond)
+ time.Sleep(s.roundTimeEstimate*2 - 200*time.Millisecond)
require.Equal(t, initialLocalRound, local.LastRound())
auth.alter(-1, false)
// wait until the catchup is done. Since we've might have missed the sleep window, we need to wait
// until the synchronization is complete.
waitStart := time.Now()
- for time.Since(waitStart) < 10*s.deadlineTimeout {
+ for time.Since(waitStart) < 10*s.roundTimeEstimate {
if remote.LastRound() == local.LastRound() {
break
}
@@ -717,7 +717,7 @@ func helperTestOnSwitchToUnSupportedProtocol(
// Make Service
s := MakeService(logging.Base(), config, net, local, &mockedAuthenticator{errorRound: -1}, nil, nil)
- s.deadlineTimeout = 2 * time.Second
+ s.roundTimeEstimate = 2 * time.Second
s.Start()
defer s.Stop()
@@ -1135,7 +1135,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) {
lookback := lookbackForStateproofsSupport(&topBlk)
oldestRound := topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback))
- assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusFuture].StateProofInterval-config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback)
+ assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval-config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback)
// the network has made progress and now it is on round 8000. in this case we would not download blocks to cover 512.
// instead, we will download blocks to confirm only the recovery period lookback.
@@ -1150,7 +1150,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) {
oldestRound = topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback))
lowestRoundToRetain := 8000 - (8000 % config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval) -
- config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*(config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals+1) - config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback
+ config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*(config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals+1) - config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback
assert.Equal(t, uint64(oldestRound), lowestRoundToRetain)
@@ -1198,7 +1198,7 @@ func TestServiceLedgerUnavailable(t *testing.T) {
cfg.CatchupParallelBlocks = 2
s := MakeService(logging.Base(), cfg, net, local, auth, nil, nil)
s.log = &periodicSyncLogger{Logger: logging.Base()}
- s.deadlineTimeout = 2 * time.Second
+ s.roundTimeEstimate = 2 * time.Second
s.testStart()
defer s.Stop()
@@ -1245,7 +1245,7 @@ func TestServiceNoBlockForRound(t *testing.T) {
s := MakeService(logging.Base(), cfg, net, local, auth, nil, nil)
pl := &periodicSyncDebugLogger{periodicSyncLogger: periodicSyncLogger{Logger: logging.Base()}}
s.log = pl
- s.deadlineTimeout = 1 * time.Second
+ s.roundTimeEstimate = 1 * time.Second
s.testStart()
defer s.Stop()
diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go
index 64dfe7329..fc7655173 100644
--- a/cmd/tealdbg/localLedger.go
+++ b/cmd/tealdbg/localLedger.go
@@ -281,6 +281,10 @@ func (l *localLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) {
return bookkeeping.BlockHeader{}, nil
}
+func (l *localLedger) GenesisHash() crypto.Digest {
+ return crypto.Digest{}
+}
+
func (l *localLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) {
return nil, fmt.Errorf("localLedger: GetStateProofVerificationContext, needed for state proof verification, is not implemented in debugger")
}
diff --git a/config/consensus.go b/config/consensus.go
index a2f28b97d..a1baa9204 100644
--- a/config/consensus.go
+++ b/config/consensus.go
@@ -162,6 +162,8 @@ type ConsensusParams struct {
// time for nodes to wait for block proposal headers for period = 0, value should be configured to suit best case
// critical path
AgreementFilterTimeoutPeriod0 time.Duration
+ // Duration of the second agreement step for period=0, value should be configured to suit best case critical path
+ AgreementDeadlineTimeoutPeriod0 time.Duration
FastRecoveryLambda time.Duration // time between fast recovery attempts
@@ -848,8 +850,9 @@ func initConsensusProtocols() {
DownCommitteeSize: 10000,
DownCommitteeThreshold: 7750,
- AgreementFilterTimeout: 4 * time.Second,
- AgreementFilterTimeoutPeriod0: 4 * time.Second,
+ AgreementFilterTimeout: 4 * time.Second,
+ AgreementFilterTimeoutPeriod0: 4 * time.Second,
+ AgreementDeadlineTimeoutPeriod0: Protocol.BigLambda + Protocol.SmallLambda,
FastRecoveryLambda: 5 * time.Minute,
@@ -1380,20 +1383,34 @@ func initConsensusProtocols() {
// for the sake of future manual calculations, we'll round that down a bit :
v37.ApprovedUpgrades[protocol.ConsensusV38] = 10000
- // ConsensusFuture is used to test features that are implemented
- // but not yet released in a production protocol version.
- vFuture := v38
+ v39 := v38
+ v39.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
- vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
+ v39.LogicSigVersion = 10
+ v39.EnableLogicSigCostPooling = true
+
+ v39.AgreementDeadlineTimeoutPeriod0 = 4 * time.Second
+
+ v39.DynamicFilterTimeout = true
+
+ v39.StateProofBlockHashInLightHeader = true
- vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here
- vFuture.EnableLogicSigCostPooling = true
+ // For future upgrades, round times will likely be shorter so giving ourselves some buffer room
+ v39.MaxUpgradeWaitRounds = 250000
- vFuture.StateProofBlockHashInLightHeader = true
+ Consensus[protocol.ConsensusV39] = v39
+
+ // v38 can be upgraded to v39, with an update delay of 7d:
+ // 157000 = (7 * 24 * 60 * 60 / 3.3 round times currently)
+ // but our current max is 150000 so using that :
+ v38.ApprovedUpgrades[protocol.ConsensusV39] = 150000
+
+ // ConsensusFuture is used to test features that are implemented
+ // but not yet released in a production protocol version.
+ vFuture := v39
+ vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
- // Setting DynamicFilterTimeout in vFuture will impact e2e test performance
- // by reducing round time. Hence, it is commented out for now.
- // vFuture.DynamicFilterTimeout = true
+ vFuture.LogicSigVersion = 11 // When moving this to a release, put a new higher LogicSigVersion here
Consensus[protocol.ConsensusFuture] = vFuture
diff --git a/config/localTemplate.go b/config/localTemplate.go
index 61c2381fa..25c6edab0 100644
--- a/config/localTemplate.go
+++ b/config/localTemplate.go
@@ -311,6 +311,12 @@ type Local struct {
// determining the source of a connection. If used, it should be set to the string "X-Forwarded-For", unless the
// proxy vendor provides another header field. In the case of CloudFlare proxy, the "CF-Connecting-IP" header
// field can be used.
+ // This setting does not support multiple X-Forwarded-For HTTP headers or multiple values in in the header and always uses the last value
+ // from the last X-Forwarded-For HTTP header that corresponds to a single reverse proxy (even if it received the request from another reverse proxy or adversary node).
+ //
+ // WARNING: By enabling this option, you are trusting peers to provide accurate forwarding addresses.
+ // Bad actors can easily spoof these headers to circumvent this node's rate and connection limiting
+ // logic. Do not enable this if your node is publicly reachable or used by untrusted parties.
UseXForwardedForAddressField string `version[0]:""`
// ForceRelayMessages indicates whether the network library should relay messages even in the case that no NetAddress was specified.
diff --git a/config/version.go b/config/version.go
index 17bd00a9b..f7e656d40 100644
--- a/config/version.go
+++ b/config/version.go
@@ -33,7 +33,7 @@ const VersionMajor = 3
// VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced.
// Not enforced until after initial public release (x > 0).
-const VersionMinor = 20
+const VersionMinor = 21
// Version is the type holding our full version information.
type Version struct {
diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json
index b37f2001c..e327e0e3b 100644
--- a/daemon/algod/api/algod.oas2.json
+++ b/daemon/algod/api/algod.oas2.json
@@ -4353,6 +4353,14 @@
"type": "string",
"format": "byte"
},
+ "clear-state-rollback": {
+ "description": "If true, indicates that the clear state program failed and any persistent state changes it produced should be reverted once the program exits.",
+ "type": "boolean"
+ },
+ "clear-state-rollback-error": {
+ "description": "The error message explaining why the clear state program failed. This field will only be populated if clear-state-rollback is true and the failure was due to an execution error.",
+ "type": "string"
+ },
"logic-sig-trace": {
"description": "Program trace that contains a trace of opcode effects in a logic sig.",
"type": "array",
diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml
index c4fb9394c..be9237b90 100644
--- a/daemon/algod/api/algod.oas3.yml
+++ b/daemon/algod/api/algod.oas3.yml
@@ -2447,6 +2447,14 @@
},
"type": "array"
},
+ "clear-state-rollback": {
+ "description": "If true, indicates that the clear state program failed and any persistent state changes it produced should be reverted once the program exits.",
+ "type": "boolean"
+ },
+ "clear-state-rollback-error": {
+ "description": "The error message explaining why the clear state program failed. This field will only be populated if clear-state-rollback is true and the failure was due to an execution error.",
+ "type": "string"
+ },
"inner-trace": {
"description": "An array of SimulationTransactionExecTrace representing the execution trace of any inner transactions executed.",
"items": {
diff --git a/daemon/algod/api/server/common/handlers.go b/daemon/algod/api/server/common/handlers.go
index 95a1dbcce..938d3ee10 100644
--- a/daemon/algod/api/server/common/handlers.go
+++ b/daemon/algod/api/server/common/handlers.go
@@ -121,14 +121,14 @@ func Ready(ctx lib.ReqContext, context echo.Context) {
// must satisfy following sub conditions:
// 1. the node is not in a fast-catchup stage
// 2. the node's time since last round should be [0, deadline),
- // while deadline = bigLambda + smallLambda = 17s
+ // while deadline = agreement.DefaultDeadlineTimeout = 17s
// 3. the node's catchup time is 0
isReadyFromStat := func(status node.StatusReport) bool {
timeSinceLastRound := status.TimeSinceLastRound().Milliseconds()
return len(status.Catchpoint) == 0 &&
timeSinceLastRound >= 0 &&
- timeSinceLastRound < agreement.DeadlineTimeout().Milliseconds() &&
+ timeSinceLastRound < agreement.DefaultDeadlineTimeout().Milliseconds() &&
status.CatchupTime.Milliseconds() == 0
}
diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go
index ef0de8085..acf5fc94b 100644
--- a/daemon/algod/api/server/v2/dryrun.go
+++ b/daemon/algod/api/server/v2/dryrun.go
@@ -22,6 +22,7 @@ import (
"strings"
"github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
@@ -241,6 +242,10 @@ func (dl *dryrunLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
return bookkeeping.BlockHeader{}, nil
}
+func (dl *dryrunLedger) GenesisHash() crypto.Digest {
+ return crypto.Digest{}
+}
+
func (dl *dryrunLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error {
return nil
}
diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go
index 1ffe766db..4a3438b8f 100644
--- a/daemon/algod/api/server/v2/generated/data/routes.go
+++ b/daemon/algod/api/server/v2/generated/data/routes.go
@@ -291,33 +291,35 @@ var swaggerSpec = []string{
"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",
- "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",
- "NHly8mjy8f8HAAD//3CL32ln9wAA",
+ "W+0VjtMGff+xtiu2yKPvWAwFv/+eSVGW8bLujegWMfXHdisw9huJvwKpmNKGEXZ9dUy3sbJqieY4LO65",
+ "tnVGBM9d9fWGCphOBOPEFpIKtUR+hlm/zr9BYFOVjldZn8SudTm9yFrEMDgD4zdmQCpROVGazUkMIswt",
+ "kUHOpTM0YnhnED3ZMFsbRxkjRBeTHCe9M+40TzEnu7l9t1ujjnN6s4kR8cIfyluQZsqSns5ovw0naU3p",
+ "fxj+EUnRPxrXaJb7e/CKqH5wu8bHo0AbpmtHyAMBSORhdjLowr7obaVRaa3yaL/3rs6++PG6dYHuTRhA",
+ "SPwHe8ALEyvb95oYdwfOZy7Z+bpBSrCU9ylK6Cx/X66mZ73NRRJskTNSaA3KsiUxFAuDRFz1oslvTWgl",
+ "gzRYbIJuNNOyjKTPWrsJnqmQcIxKINe0/PRcA7vjnyE+oHibTpoJcyhDJFtUqttVcHtFR80d5Eseb2r+",
+ "BlN2/wZmj6L3nBvKuYsHtxlavbAl9cLfCjYLmNzgmDYc6PGXZOaq6VcScqb6bugbL5w0KYMg2dyFXsJG",
+ "78lR3LfOn4W+AxnPfcwI+SFwJwk027UQtkf0MzOVxMmNUnmM+gZkEcFfjEeF3Tf3XBd3rLx+u4IgQWmv",
+ "AwuCDPuKjl2eLXphLp1awXCdo2/rDm4jF3W7trHVbEYXcL+6eqdnY4rQxIutm8+xCs5Rqq4fVHP9d6h/",
+ "Y3HkxnDzxijm51RFVFv1M1F8t7cfNSv3Boh0Sil/nE4WwEExhcWCf3HNIT7tXeohsDn5w6NqYb1LIRGL",
+ "mMhaO5MHUwVFkkfUR3afRaohY75bXkumt9gY1BvQ2C/RSj3fNVUfXNWQxnfl7j4trqFpztzWiKiVv12/",
+ "E7TE+8i61Li5hUR5Qr7Z0FVVOnMw+cu92Z/g6Z+fFY+ePv7T7M+PvniUw7Mvvnr0iH71jD7+6uljePLn",
+ "L549gsfzL7+aPSmePHsye/bk2ZdffJU/ffZ49uzLr/50z/AhA7IF1Nfufj75P9lZuRDZ2Zvz7NIA2+KE",
+ "Vux7MHuDuvJcYOM6g9QcTyKsKCsnz/1P/8ufsJNcrNrh/a8T14BlstS6Us9PT29ubk7CT04XmBSeaVHn",
+ "y1M/D7YT68grb86baHIb94I72lqPcVMdKZzhs7ffXFySszfnJy3BTJ5PHp08OnnsetdyWrHJ88lT/AlP",
+ "zxL3/dQR2+T5h4/TyekSaIk1VMwfK9CS5f6RBFps3f/VDV0sQJ5gwoD9af3k1IsVpx9ccvzHXc9Ow5CK",
+ "0w+dGgLFni8xHOD0g+9gufvtTvdCF4kVfDASil2vnc6wa8XYV0EFL6eXgsqGOv2A4nLy91Nn84g/RLXF",
+ "nodTX2gj/mYHSx/0xsC654sNK4KV5FTny7o6/YD/QeoNgLZFGE/1hp+i5/T0Q2et7vFgrd3f28/DN9Yr",
+ "UYAHTszntrPnrsenH+y/wUSwqUAyIxZi4RP3qy1QdYoNnrbDn7fc+R1LiJUV+YkrsGqrLwq/5XmbLdUc",
+ "6PPCv3yx5bmXX30wIB7TJ48e2emf4X8mrgFKr/jGqTuPk3Fd3btlD5EJ9gxnDbw2Jwz0yQRhePzpYDjn",
+ "NgDQcEXLvT9OJ198SiycG42e05Lgm3b6p59wE0CuWQ7kElaVkFSyckt+4k0MY9COMkaB11zccA+5ufrr",
+ "1YrKLYrUK7EGRVyny4A4iQQjxNg4B/TFtzSMdw9dKPQc1rOS5ZOpLXL5HsUmHZMgvDVnOJO3ZLWDd0/F",
+ "d3vPxPhd6AqmO6qKjIJzT765HX4oVQ/31+993xdqp7oX26DJvxjBvxjBERmBriVPHtHg/sLSWFC5rMic",
+ "5kvYxQ+Gt2VwwU8qEcv9v9jBLFwDihSvuOjyijbGbvL83bg2W879YC3LBShzmE+8VmFE5lbolw1H8mce",
+ "nZ/BXu/qIPzx/R/ifn9BuT/PnR23/kUqSwayoQLKhz1B/sUF/ttwAdvciNp9nRINZanCs68Fnn3rinEV",
+ "D7l1kY3kA50Cla0w3fn51BsQYjpk980PnT+7qpNa1roQN8EsaHq3fqOhlmEe1qr/9+kNZTqbC+nqImJX",
+ "9OHHGmh56pqg9H5t644PnmAx9eDHMAMx+uspdepG7Flle/AnHvZV3thTp/IlXvLhv/5xa/4KzUnIZxtD",
+ "0rv3hsthu2PHglvryPPTU8wHWQqlTycfpx96lpPw4fuGsHyXvkkl2RrL0L+fTjaZkGzBOC0zZ5VoOzlN",
+ "npw8mnz8/wEAAP//6aRdnSH5AAA=",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go
index 3fdcd1341..e7e56520a 100644
--- a/daemon/algod/api/server/v2/generated/experimental/routes.go
+++ b/daemon/algod/api/server/v2/generated/experimental/routes.go
@@ -90,210 +90,212 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go
index c30caa6c9..b9fc3b1ba 100644
--- a/daemon/algod/api/server/v2/generated/model/types.go
+++ b/daemon/algod/api/server/v2/generated/model/types.go
@@ -895,6 +895,12 @@ type SimulationTransactionExecTrace struct {
// ClearStateProgramTrace Program trace that contains a trace of opcode effects in a clear state program.
ClearStateProgramTrace *[]SimulationOpcodeTraceUnit `json:"clear-state-program-trace,omitempty"`
+ // ClearStateRollback If true, indicates that the clear state program failed and any persistent state changes it produced should be reverted once the program exits.
+ ClearStateRollback *bool `json:"clear-state-rollback,omitempty"`
+
+ // ClearStateRollbackError The error message explaining why the clear state program failed. This field will only be populated if clear-state-rollback is true and the failure was due to an execution error.
+ ClearStateRollbackError *string `json:"clear-state-rollback-error,omitempty"`
+
// InnerTrace An array of SimulationTransactionExecTrace representing the execution trace of any inner transactions executed.
InnerTrace *[]SimulationTransactionExecTrace `json:"inner-trace,omitempty"`
diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go
index 0e0d78aa4..b866cc9f4 100644
--- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go
+++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go
@@ -139,213 +139,215 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "x0cnk4//PwAA///7V+betvkAAA==",
+ "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",
+ "nUw+/v8AAAD//31f+lNw+wAA",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
index 6d7d2c417..713e5cd7b 100644
--- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
+++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
@@ -901,110 +901,112 @@ var swaggerSpec = []string{
"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",
- "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",
- "How+/t8AAAD//0RubYkspwEA",
+ "7HQl2scSGrkAIscx0g/mUnuF49RB35/XdsUWefAdi6Hg+vdMijyPl3WvRLeIqT+2W4Gx30j8BUjFlDaM",
+ "sOmrY7qOlVULNMdhcc+VrTMieOqqr1dUwHRPME5sIX2hlsjPMOvX+TcIrIvc8Srrk9i2LqcXWYsYBmdg",
+ "/MYUSCEKJ0qzGYlBhLklMsi5dIZGDO8MoicrZmvjKGOE6GKS46R3zJ3mKWZkO7dvdmvUcU5vNjEiXvhD",
+ "eQnS7LOk92e0X4aT1Kb0z4Z/RFL0D8Y1quVeB6+I6geXa3w8CLRuunaEPBCAnjzMRgZd2Be9rjQqrVUe",
+ "7ffe1dkWP17WLtCdCQMIif9gB3hhYmX9XhXj7sD5xCU7X1ZICZbyro8SGsvflavpWW91kQRb5IwUWoOy",
+ "bEl0xcIgEVc9q/Jbe7SSThosNkE3mmmeR9Jnrd0Ez1RIOEYlkCua3zzXwO74x4gPyN70J82EOZQhki0q",
+ "1eUquL2gg+YO8iUPNzV/jSm7/wCzR9F7zg3l3MWd2wytXtiSeu5vBZsFTC5wTBsO9PBrMnXV9AsJKVNt",
+ "N/SFF06qlEGQbOZCL2Gtd+Qo7lrnr0JfgYxnPmaEvArcSQLNdjWE9RH9xEyl5+RGqTxGfR2yiOAvxqPC",
+ "7ps7rosrVl6/XEGQoLTXngVBun1Fhy7PFr0wl06poLvOwbd1A7eRi7pe29BqNoMLuL99+5ueDilCEy+2",
+ "bj7HKjgHqbq+V831a6h/Y3HkxnDzxijm176KqLbqZ0/x3dZ+lCzfGSDSKKX8cTyaAwfFFBYL/t01h7jZ",
+ "u9RDYHPyu0fVwnqVQiIWMZG1NiYPpgqKJA+oj+w+i1RDxny3tJRMb7AxqDegsd+jlXp+rKo+uKohle/K",
+ "3X1anEPVnLmuEVEqf7v+KGiO95F1qXFzC4l8Qr5f02WRO3Mw+fbO9D/g8d+eZA8eP/yP6d8efPUghSdf",
+ "ffPgAf3mCX34zeOH8OhvXz15AA9nX38zfZQ9evJo+uTRk6+/+iZ9/OTh9MnX3/zHHcOHDMgWUF+7++no",
+ "v5PjfC6S49cnyZkBtsYJLdhPYPYGdeWZwMZ1BqkpnkRYUpaPnvqf/l9/wiapWNbD+19HrgHLaKF1oZ4e",
+ "HV1cXEzCT47mmBSeaFGmiyM/D7YTa8grr0+qaHIb94I7WluPcVMdKRzjszffn56R49cnk5pgRk9HDyYP",
+ "Jg9d71pOCzZ6OnqMP+HpWeC+HzliGz398HE8OloAzbGGivljCVqy1D+SQLON+7+6oPM5yAkmDNifVo+O",
+ "vFhx9MElx380M0T9bbaUdlA/2TdKKsppzlJfhoopawi2Md0qbANpLeSlGpOpbRTqw0Z5hqE9Nt9chc1y",
+ "TzKDMPv5Sc20fK9T9MeOnv4WKVjkcw18C84wWCsI4/qv059fESGJU29e0/S8yrPwiTV1MlGYV2O+nHj6",
+ "/XcJclPTl+N8VSN/zGMol4aJuISNpZoXzdqdtVQVs/p0cO1nNmQREHZVyqJmXGjiCyCp2bBhrQ+Sb959",
+ "+OpvH0cDAMG6KgqwI9t7mufvrZkM1hjL2YpYGffFEo3r0gj4Qb2TY7RIVU+Dz+t3miWv33PB4X3fNjjA",
+ "ovtA89y8KDjE9uAd9gxDYsEz9+jBA89onBgfQHfkztRoYGd2X+XdegmqUTxJXGKgLkOyj95U1Q8lLexZ",
+ "dE9spqbz09iXJobvPDngQps1Gq+83PZwnUV/RzMiXYYqLuXhF7uUE25jKM3FYi/Aj+PRV1/w3pxww3No",
+ "TvDNoCFn96L5hZ9zccH9m0b4KZdLKjco2uiKF7Y7SNC5Qucoskh7toMCW3w+evex99Y7CoMFjz40quNk",
+ "V7oTrbek0X9lxzV5R/VxThzL5kG5H+4eFwXGSp5Wz4+Lwvb3xXgAYHj7wZopre5NyI/h1w0nh4XE+ji8",
+ "OcXcelW7W99Et+HzDhrnRS/tRt757f39ae/v46axo9GXPgZM4xRshakTdXTVC7SblhJUwdk3kLiqgOxE",
+ "i8Q1SRo4hu+6f7AOYAOKX9iZ3sVUwZ2M+hZ3PbjrE5MCeCuJqW4/djOs2RdTrW6SxpVxjYz7Cxf6XtLc",
+ "0Emw3FbTkpPnt8LgX0oYrIouzq10VhQHEA8xm+Hog6sSeAiREHXfQcJgqFYH3wYR6Xdb7OTehBy337kc",
+ "z3BVFneKeea9WwHvcxDwbJnKXaKdo+NPKtSFyVD75CY1pBHz+6CPv3Ap7i+MrF6xzUC6W2C7BPvsCGOO",
+ "WV8bW/1TCmEOabfi119a/KpqH19JAAsDVI9cbn7gxrqS9a5tnWO6ksSa9a8DzoblKzBL3R7hcR2Mb1iM",
+ "jTJ28cVq7DVDdKdapdFu1rijN3ZFrB8hVFC/25w83yVdfUF2nsFtbCO3QHxvrpuXRt0Ob27G7TCMNz15",
+ "8OTmIAh34ZXQ5Ae8xa+ZQ14rS4uT1b4sbBtHOpqK9S6uxFtsqSp4Zg5tg0dVdS3HwXPzto3SuIt5sM3G",
+ "R/cm5Dv3al0bw+V5z4VhVD6fi8q5/cjwOoMMcsf/+RTHvzMhP2CWolZjDDbD9Ad8kXH99OGjx0/cK5Je",
+ "2Fiu9nvTr588Pf72W/daIRnXGA9g9ZzO60rLpwvIc+E+cHdEd1zz4Ol///N/JpPJnZ1sVay/27yynVI/",
+ "F946jlXQqwigb7e+8E2Kaeuug+1O1N2I+/47sY7eAmJ9ewt9slvIYP9PcftMm2TkFNHKktnopXLA28ge",
+ "k33uo7G7fzDVorpMJuSVcG2typxKWzUFS7IqMi+ppFwDZBNPqZgnp2wbnzRnmOAviQK5ApkoVpU+LiVU",
+ "pT0KCSuMka+LhjYg2M3oMZL2s2XyL+k6SG6fVte0Fm7JaPZc0jXBPg2aKNBjW1dsTb79ljwY19pLnpsB",
+ "kgoxMea6pOvRDVr9KmIbWiznucOOkLsDdHHsIRakWvqp6hXWqsZfnXN/sZK7JXe3sQfinHs7fmrHTmhH",
+ "cM2jtloQrGCnsbquKosi39R1VY2U50WoOIszMww1DnzGPoKdpumoEtpG7+0hvjUCXImVtAlqT7aBWafq",
+ "6APq5SHP6JxbzJr7a7lLA9+RFEvvPBJkBjpduITdFuoj7Em6pMF+3rRknC0NlA/G1y7V4C52qwKHvXsz",
+ "atPkh7SHCnIp0YEHMkLEP/tu9uYxm9lS4b6BhK/xh64pV225aphplW/bQtfF8/u83oI2GoDuhvJZPXlX",
+ "IEO0HML/eYvg/RDcYY7fu5oE9ni5RfwZIv69KpmQV6JOG7ca1J/S9XidN/t1L+iV4GB97EbytbR4606t",
+ "xA7DOCxSfL0Qq7/U7ZouK4Ic+To7W+WQv5uXdsgiQ25vrNnzJV7hf49WI2rcMmZtk53FEOrRhjBn86Lt",
+ "EhCWK5l8Si3mk/DTz1C1+RQc62ZYDB5Sz2ecWMAPy3SwBI8l5qOqaXwfB3phXg7kMluVaDA30qIKQ4NI",
+ "7R8yhVzwufo8WdE26ojjJUIlttKUbTbSWf/kL3h2n7lOIL4Zu6v3pBhPgSixBFQZjIyO3SlssOSTB3+7",
+ "OQg1W/rOyzzMXf3E3OWrB49vbvpTkCuWAjmDZSEklSzfkF941fHjKtxOEer2PLQGR5gD4+htatYFS8Mi",
+ "Rpdngo3QtQ96zbKPu5lhUEhxTz7IeMAHw/LntCiAysszwN2uq3Z70JPnYXSwqEqN+F3pAcWgaM8A+f8z",
+ "Gmh3wrR3MXOXX8ktoL76l2MTLnRXzMZVcIyRAsTsKXnL7xO1oL44pfvz0Vdf91jOzDyuaE/XdlYPZB7b",
+ "YYYY0L5oc+BhpfYKv09verf328TxiGXrWF/yDNZB0fdm+0Inlt1RpKAbH0bbKUJVxAtRVtJAOOwSjBiv",
+ "Fqy4+WKHSrNpvNqrV3+qNrgn/LtKC7YV+YzwXXyKInfjkZYAGRR6sbP2Jb5V7ya4KphMuX4FtkLhmLAJ",
+ "TGwBv7qPSzYHZTVqSnKgs6ohixBDkicCPmMIzVNFgPVwIUN00ij9YMEQJMqbV07rJAN70Xnkydad80kF",
+ "Xf2plNQEdVTgXrBpouXTyZRY6XocuLsLKbRIRW5jV8qiEFJXp1tNBol70Oe2a0h7fYR7JWFuzTK10452",
+ "hm8dwJDWpGz1xdjRzjyaYoa02KIuWZGvnmsISzsTBem03zUgfFK+dmt0i/Gzls3tSze56V7SO7AFLqU6",
+ "XZTF0Qf8D1Yk/FgnSmGtdnWk1/wIu2Edfdga0oQsNTeyibRl3ht6dLSZd9esh5/XJeV/ELLdt3RnyFIL",
+ "aeP2pW87e2HsU4Q9Xo82+ZdWwrbaK1sbfnUXXGTEznmt8oCD/kQV7QaNCnxqr+1OFiHhW5fx57Wg2og7",
+ "YzwjNNjGlq2p6iDsdYC/fbGL/hR24Zv3k3/1BZ+zV0KTk2WRwxK4huxq0YakzeH87bH1ut1PMHBXfzck",
+ "sXvnhze+D6SuZJGdF/week9QOgL8dFRiLQdzV1+PunN7k3/eN/kzXyK9QYa39/KXcy9LH/59ewV//lfw",
+ "4y92NdfoOB54Jfub6NLXcK2J73khd4QBZ8NqGQ62+ZVR9W6vUv0gpG/Hc3uLf6FOUbuTg5Msh1hodlli",
+ "3ZSHCPX/rKAfZmfI84iloe+gjm1vMr0AhkWyRMqw38FJpsb2EDvjhDvFt4LPZy34BHt9K/fcmh6+MNND",
+ "j5TjtP48HyJo7CsArZYiA+9YFbOZK0rZJ/00e2UZ8lSaLgtiv4xKOdYJy5Zwat782U5x0Cu2BrslFrXA",
+ "M8hSkAqeqQFRHG7Uy95D6GjqB+DGPZvVDnhYXLmKyaVJ9k1Q86pDCaSNfIU9znxxToeMDFbEEODkAGR7",
+ "9MH+i+a0QqjIak49AXc25q7bFltt1I7bAJC8RiHUli31X4kZeWCLjpYcMwvrZqbYfFxujKDqayxJoDlJ",
+ "GxlFFRzdk3Pae3J2qgKd1fWsKa4LiPqEHjKCoZXN+dONH4BnlDuS7yJIC0IJhznVbAXe5T+5rQBy6dvM",
+ "1d/YwgDHhGaZPY31JsAK5IaocqqMrMObgeF3VPO87MEwYF2AZOaKpnntgLdqwpEt77EtjujUvnHFS6vF",
+ "i2xREdmMWvQ3qys5ImbkJUulOM7nQvk4VLVRGpadVqHu0997ikR7Q0I3ZlXwnHFIloLHGlj+jE9f4sPY",
+ "11gipe/jM/Ow79vWfduEvwVWc54hd/JV8fuZnP4rBbq0ViuhENJot1PbVNvS/55HyR+aDU+7J2nD08Cp",
+ "5R4GA4XtLhs/H/l0hEbzy+ibHxp/ujJA7k21KHUmLoJZ0AZgwxmHVAAJWvBfwubWamWvrtfqdp3epgAP",
+ "sbNVPY00Nawf9vc1/ItmvjnnTEgkGJSeihVI1VLkbtPf/lTpb4P3fS9ubJv47uJopTqs7PJKZGDHbfbQ",
+ "jlWe5yID12u4K7JUYZHxlCF/f9XvtZI4UlrOF5qUBdEili5Sf5jQ1DLZxCpC8QmDWo9WXcLpFnQFhObY",
+ "wZlMATgRU7Po+ibFRVKF1TZ9zokL/owKTQFchRQpKAVZ4ivt7wKt6uCMoep6C54QcAS4moUoQWZUXhnY",
+ "89VOOM9hk6AyrMjdn341qvWNw2uFxu2ItTX+Iuit6gg5ubAL9bDptxFce/KQ7KgE4kUDTJETyyIHlyQX",
+ "QeFeOOndvzZEnV28Olowi4xdM8X7Sa5GQBWo10zvV4W2LBJzf3dBfGafnrElSmKccuEtkLHBcqp0sost",
+ "m5fCtSizgoATxjgxDtyjmr6gSr9x+dIZ1tay1wnOY2VsM0U/wFXP/tjIv9qHsbFTcx9yVSriRvA5UJDF",
+ "1sBhvWWuV7Cu5sKEdT92lWRlbYG7Ru7DUjC+Q1bQboBQHfj9zXCRxaGlkjpTRheVDSBqRGwD5NS/FWA3",
+ "dPj3AMJUjWhLOFg+OaScqRA5UG5zVUVRGG6hk5JX3/Wh6dS+fax/qd/tEhfV9b2dCVBhApyD/MJiVqEp",
+ "d0EVcXCQJT13OXJz1z6uC7M5jAnWtki2UT4ad81b4RHYeUjLYi5pBkkGOY0YXX6xj4l9vG0A3HFPnslK",
+ "aEimMBMS4pteU7LsNSZVQwscT8WER4JPSGqOoFGeawJxX+8YOQMcO8acHB3dqYbCuaJb5MfDZdut7jFg",
+ "mTHMjjt6QJAdRx8CcA8eqqEvjwr8OKnNB+0p/gnKTVDJEftPsgHVt4R6/L0W0Db8hRdY46ZosfcWB46y",
+ "zV42toOP9B3ZmKnxi3QLtKOcrjHJrmlqDRTAyWWU26MLynQyE9IK0gmdaZA7Q+f/QZl3nPv0XeGqrhAc",
+ "wd2bbhxk8mETH8dFLAjEXReGRCbkbAESzB1GyUOyZLzU9oko9djWHJVA04UR2kMbrB0J2zC6xoQS5lRm",
+ "Obbom1X3ppB4GTHduuAR6Eg+YlPjN+v+QchBlYyb9boo06TkmuVBN4dKb//8rJe3Folbi8StReLWInFr",
+ "kbi1SNxaJG4tErcWiVuLxK1F4tYi8de1SHyqMkmJlzh8xUYueNIOpryNpfxTlfKtripvIEHrxAVl2vUm",
+ "9lUK+u0WexiCNNAcccBy6I/utkGnZ98fvyBKlDIFkhoIGSdFTo1qAGtddcps9mD23eFtu13b3pkqePyI",
+ "nP792FccXbjKmM137x7beDWi9CaHe64XDfDMSqK+KQ1wg3TXk4b6K8F31HT9RVmOkfGKfI9vP4cV5KIA",
+ "aYsZEi3LSEv6M6D5M4ebHQaff5jJXajtezPa+3HD6OXQtqSFF/P9Wqki1GZckudBDub7Gc0VvO9Lw7Tj",
+ "LWkRa2pZXXzWFITM5DuRbVonxOzaEW5g82zUdUcZp3ITqRLVTYFok4YWhl05wurasj4evDpul2i7ZLaL",
+ "wmLSugQVPcfbqDxaFrbasM5QNlF31qKTUSzHtF0LdVQBOKgwIKZJ2D0hb+x3n7YMIELkjljNzD+bKMbm",
+ "mxXTwHeNEuFYz5eaS+ARHz29ePbHhrCzMgXCtCK+wO7u62U8WidmpDnwxDGgZCqyTdJgX6PGLZQxRZWC",
+ "5XT3TRTyT9fG3V0+5sn2e+rTXCPPg8Vt48kh0awTx4B7uPNGw2DeXGELR3TsOcD4dbPoPjYagkAcf4oZ",
+ "lVq8b1+mV0+zuWV8t4wvOI0tiYBxV5C8zUQm18j45EaWvJ/nfb+GtDTAhSf5Llrn0SUHa91wsmYwLedz",
+ "bEff8dGZpQGOxwT/RKzQLncoF9yPguzgVYviqyapt4frcpcgb/yur8x4D7eD8g06M5YF5Rvv8oVEsWWZ",
+ "WxzaTp6HZbS2ZnisxHRt++uzar/2Jr/Aduuu2ubvFi3kgipi9xcyUvLMZTx1aluv+fA6J3boszWv2fTW",
+ "miZ2vZHVuXmHXBF+l5up5ooUIBO95vZANQ6T62BgT+7ktg33X+PasInq0MNgu9X4a4ZwoNtDBnwNr4+g",
+ "51KdmNfoxESb6YSNZ2jR6E9xCZsz2TcPGljSGb4ZX1KbW5z/FPKCUJLmDL2rgisty1S/5RT9N8HCJt3Y",
+ "E2+o7ud9z/wrcRdixMPnhnrLKQYZVV6dKA+cQcSF8QOAZ7GqnM9BGT4aEtAM4C13bzFOSm60MDEjS5ZK",
+ "kdjUWnO+jOwysW8u6YbMsKKJIH+AFGRqbv1g160tWWmW5y7YxUxDxOwtp5rkQJUmL5nhwGY4X06hCjkD",
+ "fSHkeYWFeK+eOXBQTCVxw8yP9im2w3HL9wZANGbax3Ubi5vtg+NhZ1kv5CfPMUYNqzHnTOk6PqID+435",
+ "xpeMJ1EiO1sAceFibdoid7EGnCOge03HkV7AW25uPy0IcnyqL0cObQ9Q5yza09GimsZGtBxFfq2D1L+D",
+ "cBkSYTK3bpc/UQppQAfes4kbb+vrt/Z+TxdL48oFnpmnPReyferaJ/a85BSIhpGsVeDGvXHWAHmr/+LL",
+ "Lyt5eF3So/Fg2mR3wC67ajbIQ7z5DR8Tmgs+t3UVjXYpcJ8YL0qNAeDXacCDFc0TsQIpWQZq4EqZ4N+v",
+ "aP5z9dnH8QjWkCZa0hQSa1EYirUz842lU2w0yJlmNE9Qqx4KEJzYr07tRzvu46Db6HIJGaMa8g0pJKSQ",
+ "2UJkTJFan5/YAg0kXVA+x6tbinK+sK/ZcS5AQtWY0ajQ7SHihWDWPLFF6bowHhNrCw3r9gJNF5HGMXjB",
+ "GZ3dE1TW6Ek1cA8aJUf7lPTxqFfQNkhd1aFzFjlNNjNAimjIAwF+6okPUaP1luhvif5LJ/pYSUVE3axl",
+ "rbD4Crflms1a111A9AatZJ+kuvBtif4/e4l+z4EUoUTShg4S7w1HFWGaXGBZpCkQc3+VaJ13Dfecvo6Z",
+ "dsFRd5U2lWvPly4o466mTpXXgHBo1y1e+/a012LYtMwMLZoGHZCWkukNai20YL+fg/n/OyP2K5Arr9CU",
+ "Mh89HS20Lp4eHeUipflCKH00+jgOn6nWw3cV/B+8LlJItjL61UcEW0g2Z9zcuRd0PgdZmxBHjyYPRh//",
+ "bwAAAP//bsB3VeaoAQA=",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go
index f352c24c7..c0e0c71fe 100644
--- a/daemon/algod/api/server/v2/generated/participating/private/routes.go
+++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go
@@ -203,216 +203,218 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go
index 107ad4eba..093883352 100644
--- a/daemon/algod/api/server/v2/generated/participating/public/routes.go
+++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go
@@ -354,45 +354,47 @@ var swaggerSpec = []string{
"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",
- "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",
- "SIoQAQA=",
+ "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",
+ "/G1EEgEA",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go
index bc7e71527..b82212a19 100644
--- a/daemon/algod/api/server/v2/utils.go
+++ b/daemon/algod/api/server/v2/utils.go
@@ -450,12 +450,14 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra
return nil
}
return &model.SimulationTransactionExecTrace{
- ApprovalProgramTrace: sliceOrNil(convertSlice(txnTrace.ApprovalProgramTrace, convertOpcodeTraceUnit)),
- ApprovalProgramHash: digestOrNil(txnTrace.ApprovalProgramHash),
- ClearStateProgramTrace: sliceOrNil(convertSlice(txnTrace.ClearStateProgramTrace, convertOpcodeTraceUnit)),
- ClearStateProgramHash: digestOrNil(txnTrace.ClearStateProgramHash),
- LogicSigTrace: sliceOrNil(convertSlice(txnTrace.LogicSigTrace, convertOpcodeTraceUnit)),
- LogicSigHash: digestOrNil(txnTrace.LogicSigHash),
+ ApprovalProgramTrace: sliceOrNil(convertSlice(txnTrace.ApprovalProgramTrace, convertOpcodeTraceUnit)),
+ ApprovalProgramHash: digestOrNil(txnTrace.ApprovalProgramHash),
+ ClearStateProgramTrace: sliceOrNil(convertSlice(txnTrace.ClearStateProgramTrace, convertOpcodeTraceUnit)),
+ ClearStateProgramHash: digestOrNil(txnTrace.ClearStateProgramHash),
+ ClearStateRollback: omitEmpty(txnTrace.ClearStateRollback),
+ ClearStateRollbackError: omitEmpty(txnTrace.ClearStateRollbackError),
+ LogicSigTrace: sliceOrNil(convertSlice(txnTrace.LogicSigTrace, convertOpcodeTraceUnit)),
+ LogicSigHash: digestOrNil(txnTrace.LogicSigHash),
InnerTrace: sliceOrNil(convertSlice(txnTrace.InnerTraces,
func(trace simulation.TransactionTrace) model.SimulationTransactionExecTrace {
return *convertTxnTrace(&trace)
diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go
index 2b4fa8c81..a4cc3d638 100644
--- a/data/bookkeeping/block_test.go
+++ b/data/bookkeeping/block_test.go
@@ -832,9 +832,9 @@ func TestBlock_ContentsMatchHeader(t *testing.T) {
copy(block.BlockHeader.TxnCommitments.Sha256Commitment[:], rootSliceSHA256)
a.False(block.ContentsMatchHeader())
- /* Test Consensus Future */
+ /* Test Consensus Current */
// Create a block with SHA256 TxnCommitments
- block.CurrentProtocol = protocol.ConsensusFuture
+ block.CurrentProtocol = protocol.ConsensusCurrentVersion
block.BlockHeader.TxnCommitments.NativeSha512_256Commitment = crypto.Digest{}
block.BlockHeader.TxnCommitments.Sha256Commitment = crypto.Digest{}
diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md
index 3ba9037fc..a38ba1462 100644
--- a/data/transactions/logic/README.md
+++ b/data/transactions/logic/README.md
@@ -633,6 +633,7 @@ Global fields are fields that are common to all the transactions in the group. I
| 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. |
| 15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. |
| 16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. |
+| 17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. |
**Asset Fields**
@@ -751,6 +752,12 @@ Account fields used in the `acct_params_get` opcode.
### Box Access
+Box opcodes that create, delete, or resize boxes affect the minimum
+balance requirement of the calling application's account. The change
+is immediate, and can be observed after exection by using
+`min_balance`. If the account does not possess the new minimum
+balance, the opcode fails.
+
All box related opcodes fail immediately if used in a
ClearStateProgram. This behavior is meant to discourage Smart Contract
authors from depending upon the availability of boxes in a ClearState
@@ -763,13 +770,15 @@ are sure to be _available_.
| Opcode | Description |
| - | -- |
-| `box_create` | create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 |
+| `box_create` | create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 |
| `box_extract` | read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. |
| `box_replace` | write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. |
+| `box_splice` | set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C. |
| `box_del` | delete box named A if it exists. Return 1 if A existed, 0 otherwise |
| `box_len` | X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. |
| `box_get` | X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. |
| `box_put` | replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist |
+| `box_resize` | change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768. |
### Inner Transactions
diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md
index e98d6c244..31f8fb05b 100644
--- a/data/transactions/logic/README_in.md
+++ b/data/transactions/logic/README_in.md
@@ -406,6 +406,12 @@ Account fields used in the `acct_params_get` opcode.
### Box Access
+Box opcodes that create, delete, or resize boxes affect the minimum
+balance requirement of the calling application's account. The change
+is immediate, and can be observed after exection by using
+`min_balance`. If the account does not possess the new minimum
+balance, the opcode fails.
+
All box related opcodes fail immediately if used in a
ClearStateProgram. This behavior is meant to discourage Smart Contract
authors from depending upon the availability of boxes in a ClearState
diff --git a/data/transactions/logic/TEAL_opcodes_v10.md b/data/transactions/logic/TEAL_opcodes_v10.md
index 4ec00a52b..81d742fbd 100644
--- a/data/transactions/logic/TEAL_opcodes_v10.md
+++ b/data/transactions/logic/TEAL_opcodes_v10.md
@@ -461,6 +461,7 @@ Fields
| 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. |
| 15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. |
| 16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. |
+| 17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. |
## gtxn
@@ -1487,7 +1488,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with
- Bytecode: 0xb9
- Stack: ..., A: boxName, B: uint64 &rarr; ..., bool
-- create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1
+- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1
- Availability: v8
- Mode: Application
@@ -1641,6 +1642,24 @@ Fields
| 1 | BlkTimestamp | uint64 | |
+## box_splice
+
+- Bytecode: 0xd2
+- Stack: ..., A: boxName, B: uint64, C: uint64, D: []byte &rarr; ...
+- set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C.
+- Availability: v10
+- Mode: Application
+
+Boxes are of constant length. If C < len(D), then len(D)-C bytes will be removed from the end. If C > len(D), zero bytes will be appended to the end to reach the box length.
+
+## box_resize
+
+- Bytecode: 0xd3
+- Stack: ..., A: boxName, B: uint64 &rarr; ...
+- change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768.
+- Availability: v10
+- Mode: Application
+
## ec_add
- Syntax: `ec_add G` ∋ G: [EC](#field-group-ec)
diff --git a/data/transactions/logic/TEAL_opcodes_v8.md b/data/transactions/logic/TEAL_opcodes_v8.md
index b8efb37fa..71d756a1d 100644
--- a/data/transactions/logic/TEAL_opcodes_v8.md
+++ b/data/transactions/logic/TEAL_opcodes_v8.md
@@ -1485,7 +1485,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with
- Bytecode: 0xb9
- Stack: ..., A: boxName, B: uint64 &rarr; ..., bool
-- create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1
+- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1
- Availability: v8
- Mode: Application
diff --git a/data/transactions/logic/TEAL_opcodes_v9.md b/data/transactions/logic/TEAL_opcodes_v9.md
index 54f053686..f2ff33059 100644
--- a/data/transactions/logic/TEAL_opcodes_v9.md
+++ b/data/transactions/logic/TEAL_opcodes_v9.md
@@ -1485,7 +1485,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with
- Bytecode: 0xb9
- Stack: ..., A: boxName, B: uint64 &rarr; ..., bool
-- create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1
+- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1
- Availability: v8
- Mode: Application
diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go
index 19c75d201..c45c0af77 100644
--- a/data/transactions/logic/assembler_test.go
+++ b/data/transactions/logic/assembler_test.go
@@ -430,7 +430,15 @@ pushbytess "1" "2" "1"
const v8Nonsense = v7Nonsense + switchNonsense + frameNonsense + matchNonsense + boxNonsense
const v9Nonsense = v8Nonsense
-const v10Nonsense = v9Nonsense + pairingNonsense
+
+const spliceNonsence = `
+ box_splice
+ box_resize
+`
+
+const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence
+
+const v11Nonsense = v10Nonsense
const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a"
@@ -447,7 +455,12 @@ const matchCompiled = "83030102018e02fff500008203013101320131"
const v8Compiled = v7Compiled + switchCompiled + frameCompiled + matchCompiled + boxCompiled
const v9Compiled = v8Compiled
-const v10Compiled = v9Compiled + pairingCompiled
+
+const spliceCompiled = "d2d3"
+
+const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled
+
+const V11Compiled = v10Compiled
var nonsense = map[uint64]string{
1: v1Nonsense,
@@ -460,20 +473,21 @@ var nonsense = map[uint64]string{
8: v8Nonsense,
9: v9Nonsense,
10: v10Nonsense,
+ 11: v11Nonsense,
}
var compiled = map[uint64]string{
- 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494",
- 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f",
- 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e",
- 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164",
- 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03",
- 6: "06" + v6Compiled,
- 7: "07" + v7Compiled,
- 8: "08" + v8Compiled,
- 9: "09" + v9Compiled,
-
+ 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494",
+ 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f",
+ 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e",
+ 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164",
+ 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03",
+ 6: "06" + v6Compiled,
+ 7: "07" + v7Compiled,
+ 8: "08" + v8Compiled,
+ 9: "09" + v9Compiled,
10: "0a" + v10Compiled,
+ 11: "0b" + V11Compiled,
}
func pseudoOp(opcode string) bool {
@@ -527,7 +541,7 @@ func TestAssemble(t *testing.T) {
}
}
-var experiments = []uint64{pairingVersion}
+var experiments = []uint64{}
// TestExperimental forces a conscious choice to promote "experimental" opcode
// groups. This will fail when we increment vFuture's LogicSigVersion. If we had
@@ -1677,17 +1691,19 @@ txn NumApprovalProgramPages
txna ApprovalProgramPages 0
txn NumClearStateProgramPages
txna ClearStateProgramPages 0
+pushint 1
+block BlkTimestamp
+pushint 1
+block BlkSeed
global AssetCreateMinBalance
global AssetOptInMinBalance
+global GenesisHash
`, AssemblerMaxVersion)
- for _, globalField := range GlobalFieldNames {
- if !strings.Contains(text, globalField) {
- t.Errorf("TestAssembleDisassemble missing field global %v", globalField)
- }
- }
- for _, txnField := range TxnFieldNames {
- if !strings.Contains(text, txnField) {
- t.Errorf("TestAssembleDisassemble missing field txn %v", txnField)
+ for _, names := range [][]string{GlobalFieldNames[:], TxnFieldNames[:], blockFieldNames[:]} {
+ for _, f := range names {
+ if !strings.Contains(text, f) {
+ t.Errorf("TestAssembleDisassemble missing field %v", f)
+ }
}
}
ops := testProg(t, text, AssemblerMaxVersion)
diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go
index ad371c6a1..4938f95aa 100644
--- a/data/transactions/logic/box.go
+++ b/data/transactions/logic/box.go
@@ -35,6 +35,8 @@ const (
BoxWriteOperation
// BoxDeleteOperation deletes a box
BoxDeleteOperation
+ // BoxResizeOperation resizes a box
+ BoxResizeOperation
)
func (cx *EvalContext) availableBox(name string, operation BoxOperation, createSize uint64) ([]byte, bool, error) {
@@ -81,6 +83,13 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS
cx.available.dirtyBytes += writeSize
}
dirty = true
+ case BoxResizeOperation:
+ newSize := createSize
+ if dirty {
+ cx.available.dirtyBytes -= uint64(len(content))
+ }
+ cx.available.dirtyBytes += newSize
+ dirty = true
case BoxDeleteOperation:
if dirty {
cx.available.dirtyBytes -= uint64(len(content))
@@ -199,6 +208,34 @@ func opBoxReplace(cx *EvalContext) error {
return cx.Ledger.SetBox(cx.appID, name, bytes)
}
+func opBoxSplice(cx *EvalContext) error {
+ last := len(cx.Stack) - 1 // replacement
+ replacement := cx.Stack[last].Bytes
+ length := cx.Stack[last-1].Uint
+ start := cx.Stack[last-2].Uint
+ name := string(cx.Stack[last-3].Bytes)
+
+ err := argCheck(cx, name, 0)
+ if err != nil {
+ return err
+ }
+
+ contents, exists, err := cx.availableBox(name, BoxWriteOperation, 0 /* size is already known */)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return fmt.Errorf("no such box %#x", name)
+ }
+
+ bytes, err := spliceCarefully(contents, replacement, start, length)
+ if err != nil {
+ return err
+ }
+ cx.Stack = cx.Stack[:last-3]
+ return cx.Ledger.SetBox(cx.appID, name, bytes)
+}
+
func opBoxDel(cx *EvalContext) error {
last := len(cx.Stack) - 1 // name
name := string(cx.Stack[last].Bytes)
@@ -222,6 +259,48 @@ func opBoxDel(cx *EvalContext) error {
return nil
}
+func opBoxResize(cx *EvalContext) error {
+ last := len(cx.Stack) - 1 // size
+ prev := last - 1 // name
+
+ name := string(cx.Stack[prev].Bytes)
+ size := cx.Stack[last].Uint
+
+ err := argCheck(cx, name, size)
+ if err != nil {
+ return err
+ }
+
+ contents, exists, err := cx.availableBox(name, BoxResizeOperation, size)
+ if err != nil {
+ return err
+ }
+
+ if !exists {
+ return fmt.Errorf("no such box %#x", name)
+ }
+ appAddr := cx.GetApplicationAddress(cx.appID)
+ _, err = cx.Ledger.DelBox(cx.appID, name, appAddr)
+ if err != nil {
+ return err
+ }
+ var resized []byte
+ if size > uint64(len(contents)) {
+ resized = make([]byte, size)
+ copy(resized, contents)
+ } else {
+ resized = contents[:size]
+ }
+ err = cx.Ledger.NewBox(cx.appID, name, resized, appAddr)
+ if err != nil {
+ return err
+ }
+
+ cx.Stack = cx.Stack[:prev]
+ return err
+
+}
+
func opBoxLen(cx *EvalContext) error {
last := len(cx.Stack) - 1 // name
name := string(cx.Stack[last].Bytes)
@@ -292,3 +371,34 @@ func opBoxPut(cx *EvalContext) error {
appAddr := cx.GetApplicationAddress(cx.appID)
return cx.Ledger.NewBox(cx.appID, name, value, appAddr)
}
+
+// spliceCarefully is used to make a NEW byteslice copy of original, with
+// replacement written over the bytes from start to start+length. Returned slice
+// is always the same size as original. Zero bytes are "shifted in" or high
+// bytes are "shifted out" as needed.
+func spliceCarefully(original []byte, replacement []byte, start uint64, olen uint64) ([]byte, error) {
+ if start > uint64(len(original)) {
+ return nil, fmt.Errorf("replacement start %d beyond length: %d", start, len(original))
+ }
+ oend := start + olen
+ if oend < start {
+ return nil, fmt.Errorf("splice end exceeds uint64")
+ }
+
+ if oend > uint64(len(original)) {
+ return nil, fmt.Errorf("splice end %d beyond original length: %d", oend, len(original))
+ }
+
+ // Do NOT use the append trick to make a copy here.
+ // append(nil, []byte{}...) would return a nil, which means "not a bytearray" to AVM.
+ clone := make([]byte, len(original))
+ copy(clone[:start], original)
+ copied := copy(clone[start:], replacement)
+ if copied != len(replacement) {
+ return nil, fmt.Errorf("splice inserted bytes too long")
+ }
+ // If original is "too short" we get zeros at the end. If original is "too
+ // long" we lose some bytes. Fortunately, that's what we want.
+ copy(clone[int(start)+copied:], original[oend:])
+ return clone, nil
+}
diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go
index 5f08878a9..664962744 100644
--- a/data/transactions/logic/box_test.go
+++ b/data/transactions/logic/box_test.go
@@ -41,11 +41,15 @@ func TestBoxNewDel(t *testing.T) {
ep, txn, ledger := MakeSampleEnv()
createSelf := fmt.Sprintf(`byte "self"; int %d; box_create;`, size)
+ growSelf := fmt.Sprintf(`byte "self"; int %d; box_resize; int 1`, size+5)
createOther := fmt.Sprintf(`byte "other"; int %d; box_create;`, size)
ledger.NewApp(txn.Sender, 888, basics.AppParams{})
+ TestApp(t, growSelf, ep, "no such box")
+
TestApp(t, createSelf, ep)
+ TestApp(t, growSelf, ep)
ledger.DelBoxes(888, "self")
TestApp(t, createSelf+`assert;`+createSelf+`!`, ep)
@@ -77,10 +81,13 @@ func TestBoxNewBad(t *testing.T) {
ledger.NewApp(txn.Sender, 888, basics.AppParams{})
TestApp(t, `byte "self"; int 999; box_create`, ep, "write budget")
- // In test proto, you get 100 I/O budget per boxref
+ // In test proto, you get 100 I/O budget per boxref, and 1000 is the
+ // absolute biggest box.
ten := [10]transactions.BoxRef{}
txn.Boxes = append(txn.Boxes, ten[:]...) // write budget is now 11*100 = 1100
TestApp(t, `byte "self"; int 999; box_create`, ep)
+ TestApp(t, `byte "self"; int 1000; box_resize; int 1`, ep)
+ TestApp(t, `byte "self"; int 1001; box_resize; int 1`, ep, "box size too large")
ledger.DelBoxes(888, "self")
TestApp(t, `byte "self"; int 1000; box_create`, ep)
ledger.DelBoxes(888, "self")
@@ -139,6 +146,73 @@ func TestBoxReadWrite(t *testing.T) {
"no such box")
TestApp(t, `byte "junk"; int 1; byte 0x3031; box_replace`, ep,
"invalid Box reference")
+
+ TestApp(t, `byte "self"; int 1; int 2; byte 0x3031; box_splice`, ep,
+ "no such box")
+ TestApp(t, `byte "junk"; int 1; int 2; byte 0x3031; box_splice`, ep,
+ "invalid Box reference")
+}
+
+func TestBoxSplice(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ ep, txn, ledger := MakeSampleEnv()
+
+ ledger.NewApp(txn.Sender, 888, basics.AppParams{})
+ // extract some bytes until past the end, confirm the begin as zeros, and
+ // when it fails.
+ TestApp(t, `byte "self"; int 4; box_create;`, ep)
+
+ // replace two bytes with two bytes. would usually use box_replace
+ TestApp(t, `byte "self"; int 1; int 2; byte 0x5555; box_splice;
+ byte "self"; box_get; assert; byte 0x00555500; ==`, ep)
+
+ // replace first 55 with two 44s.
+ TestApp(t, `byte "self"; int 1; int 1; byte 0x4444; box_splice;
+ byte "self"; box_get; assert; byte 0x00444455; ==`, ep)
+
+ // replace second 44 with two 33s. (loses the 55)
+ TestApp(t, `byte "self"; int 2; int 1; byte 0x3333; box_splice;
+ byte "self"; box_get; assert; byte 0x00443333; ==`, ep)
+
+ // replace 0044 with 22. (shifts in a 0x00)
+ TestApp(t, `byte "self"; int 0; int 2; byte 0x22; box_splice;
+ byte "self"; box_get; assert; byte 0x22333300; ==`, ep)
+
+ // dumb: try to replace 00 with 1111, but growing is illegal
+ TestApp(t, `byte "self"; int 3; int 1; byte 0x1111; box_splice;
+ byte "self"; box_get; assert; byte 0x2233331111; ==`, ep,
+ "inserted bytes too long")
+
+ // dumber: try to replace 00__ with 1111, but placing outside bounds is illegal
+ TestApp(t, `byte "self"; int 3; int 2; byte 0x1111; box_splice;
+ byte "self"; box_get; assert; byte 0x2233331111; ==`, ep,
+ "splice end 5 beyond original length")
+
+ // try to replace AT end (fails because it would extend)
+ TestApp(t, `byte "self"; int 4; int 0; byte 0x1111; box_splice;
+ byte "self"; box_get; assert; byte 0x223333001111; ==`, ep,
+ "splice inserted bytes too long")
+
+ // so it's ok if you splice in nothing
+ TestApp(t, `byte "self"; int 4; int 0; byte 0x; box_splice;
+ byte "self"; box_get; assert; byte 0x22333300; ==`, ep)
+
+ // try to replace BEYOND end (fails no matter what)
+ TestApp(t, `byte "self"; int 5; int 0; byte 0x1111; box_splice;
+ byte "self"; box_get; assert; byte 0x22333300001111; ==`, ep,
+ "replacement start 5 beyond length")
+
+ // even doing nothing is illegal beyond the end
+ TestApp(t, `byte "self"; int 5; int 0; byte 0x; box_splice;
+ byte "self"; box_get; assert; byte 0x22333300; ==`, ep,
+ "replacement start 5 beyond length")
+
+ // overflow doesn't work
+ TestApp(t, `byte "self"; int 2; int 18446744073709551615; byte 0x; box_splice;
+ byte "self"; box_get; assert; byte 0x22333300; ==`, ep,
+ "splice end exceeds uint64")
}
func TestBoxAcrossTxns(t *testing.T) {
@@ -167,22 +241,37 @@ func TestDirtyTracking(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- ep, txn, ledger := MakeSampleEnv()
+ ep, txn, ledger := MakeSampleEnv() // has two box refs, "self", "other" = 200 budget
ledger.NewApp(txn.Sender, 888, basics.AppParams{})
TestApp(t, `byte "self"; int 200; box_create`, ep)
+ TestApp(t, `byte "self"; int 201; box_resize; int 1`, ep, "write budget")
TestApp(t, `byte "other"; int 201; box_create`, ep, "write budget")
// deleting "self" doesn't give extra write budget to create big "other"
- TestApp(t, `byte "self"; box_del; !; byte "other"; int 201; box_create`, ep,
+ TestApp(t, `byte "self"; box_del; assert; byte "other"; int 201; box_create`, ep,
"write budget")
// though it cancels out a creation that happened here
TestApp(t, `byte "self"; int 200; box_create; assert
byte "self"; box_del; assert
- byte "self"; int 200; box_create;
+ byte "other"; int 200; box_create;
+ `, ep)
+ ledger.DelBoxes(888, "self", "other")
+
+ // create 200, but shrink it, then the write budget frees up
+ TestApp(t, `byte "self"; int 200; box_create; assert
+ byte "self"; int 150; box_resize;
+ byte "other"; int 50; box_create;
`, ep)
+ ledger.DelBoxes(888, "self", "other")
+ // confirm that the exactly right amount freed up
+ TestApp(t, `byte "self"; int 200; box_create; assert
+ byte "self"; int 150; box_resize;
+ byte "other"; int 51; box_create;
+ `, ep, "write budget")
ledger.DelBoxes(888, "self", "other")
+
// same, but create a different box than deleted
TestApp(t, `byte "self"; int 200; box_create; assert
byte "self"; box_del; assert
@@ -217,6 +306,7 @@ func TestBoxUnavailableWithClearState(t *testing.T) {
"box_len": `byte "self"; box_len`,
"box_put": `byte "put"; byte "self"; box_put`,
"box_replace": `byte "self"; int 0; byte "new"; box_replace`,
+ "box_resize": `byte "self"; int 10; box_resize`,
}
for name, program := range tests {
@@ -523,6 +613,8 @@ func TestEarlyPanics(t *testing.T) {
"box_len": `byte "%s"; box_len`,
"box_put": `byte "%s"; byte "hello"; box_put`,
"box_replace": `byte "%s"; int 0; byte "new"; box_replace`,
+ "box_splice": `byte "%s"; int 0; int 2; byte "new"; box_splice`,
+ "box_resize": `byte "%s"; int 2; box_resize`,
}
for name, program := range tests {
diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go
index eda022e72..e11b41b8a 100644
--- a/data/transactions/logic/debugger.go
+++ b/data/transactions/logic/debugger.go
@@ -98,7 +98,7 @@ func (a *debuggerEvalTracerAdaptor) BeforeOpcode(cx *EvalContext) {
}
// AfterProgram invokes the debugger's Complete hook
-func (a *debuggerEvalTracerAdaptor) AfterProgram(cx *EvalContext, evalError error) {
+func (a *debuggerEvalTracerAdaptor) AfterProgram(cx *EvalContext, pass bool, evalError error) {
if a.txnDepth > 0 {
// only report updates for top-level transactions, for backwards compatibility
return
diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go
index c060d8262..bfecb01a6 100644
--- a/data/transactions/logic/doc.go
+++ b/data/transactions/logic/doc.go
@@ -286,13 +286,15 @@ var opDescByName = map[string]OpDesc{
"frame_bury": {"replace the Nth (signed) value from the frame pointer in the stack with A", "", []string{"frame slot"}},
"popn": {"remove N values from the top of the stack", "", []string{"stack depth"}},
- "box_create": {"create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", nil},
+ "box_create": {"create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", nil},
"box_extract": {"read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "", nil},
"box_replace": {"write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "", nil},
+ "box_splice": {"set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C.", "Boxes are of constant length. If C < len(D), then len(D)-C bytes will be removed from the end. If C > len(D), zero bytes will be appended to the end to reach the box length.", nil},
"box_del": {"delete box named A if it exists. Return 1 if A existed, 0 otherwise", "", nil},
"box_len": {"X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", "", nil},
"box_get": {"X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0.", "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", nil},
"box_put": {"replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist", "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", nil},
+ "box_resize": {"change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768.", "", nil},
}
// OpDoc returns a description of the op
@@ -351,7 +353,7 @@ var OpGroups = map[string][]string{
"Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "pushints", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "pushbytess", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"},
"Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "popn", "dup", "dup2", "dupn", "dig", "bury", "cover", "uncover", "frame_dig", "frame_bury", "swap", "select", "assert", "callsub", "proto", "retsub", "switch", "match"},
"State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log", "block"},
- "Box Access": {"box_create", "box_extract", "box_replace", "box_del", "box_len", "box_get", "box_put"},
+ "Box Access": {"box_create", "box_extract", "box_replace", "box_splice", "box_del", "box_len", "box_get", "box_put", "box_resize"},
"Inner Transactions": {"itxn_begin", "itxn_next", "itxn_field", "itxn_submit", "itxn", "itxna", "itxnas", "gitxn", "gitxna", "gitxnas"},
}
diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go
index 2db51b24a..836b8cb85 100644
--- a/data/transactions/logic/eval.go
+++ b/data/transactions/logic/eval.go
@@ -200,10 +200,12 @@ func computeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 {
// "stateless" for signature purposes.
type LedgerForSignature interface {
BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
+ GenesisHash() crypto.Digest
}
-// NoHeaderLedger is intended for debugging situations in which it is reasonable
-// to preclude the use of `block` and `txn LastValidTime`
+// NoHeaderLedger is intended for debugging TEAL in isolation(no real ledger) in
+// which it is reasonable to preclude the use of `block`, `txn
+// LastValidTime`. Also `global GenesisHash` is just a static value.
type NoHeaderLedger struct {
}
@@ -212,6 +214,16 @@ func (NoHeaderLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) {
return bookkeeping.BlockHeader{}, fmt.Errorf("no block header access")
}
+// GenesisHash returns a fixed value
+func (NoHeaderLedger) GenesisHash() crypto.Digest {
+ return crypto.Digest{
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ }
+}
+
// LedgerForLogic represents ledger API for Stateful TEAL program
type LedgerForLogic interface {
AccountData(addr basics.Address) (ledgercore.AccountData, error)
@@ -1169,7 +1181,7 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) {
}
// Ensure we update the tracer before exiting
- cx.Tracer.AfterProgram(cx, tracerErr)
+ cx.Tracer.AfterProgram(cx, pass, tracerErr)
if x != nil {
// Panic again to trigger higher-level recovery and error reporting
@@ -3610,8 +3622,11 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er
sv.Uint = cx.Proto.MinBalance
case AssetOptInMinBalance:
sv.Uint = cx.Proto.MinBalance
+ case GenesisHash:
+ gh := cx.SigLedger.GenesisHash()
+ sv.Bytes = gh[:]
default:
- err = fmt.Errorf("invalid global field %d", fs.field)
+ return sv, fmt.Errorf("invalid global field %s", fs.field)
}
if fs.ftype.AVMType != sv.avmType() {
@@ -3962,7 +3977,7 @@ func replaceCarefully(original []byte, replacement []byte, start uint64) ([]byte
return nil, fmt.Errorf("replacement start %d beyond length: %d", start, len(original))
}
end := start + uint64(len(replacement))
- if end < start { // impossible because it is sum of two avm value lengths
+ if end < start { // impossible because it is sum of two avm value (or box) lengths
return nil, fmt.Errorf("replacement end exceeds uint64")
}
@@ -5587,7 +5602,7 @@ func opBlock(cx *EvalContext) error {
cx.Stack[last].Uint = uint64(hdr.TimeStamp)
return nil
default:
- return fmt.Errorf("invalid block field %d", fs.field)
+ return fmt.Errorf("invalid block field %s", fs.field)
}
}
diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go
index 06fee09c1..4682732fb 100644
--- a/data/transactions/logic/evalAppTxn_test.go
+++ b/data/transactions/logic/evalAppTxn_test.go
@@ -575,6 +575,37 @@ func TestBadField(t *testing.T) {
TestAppBytes(t, ops.Program, ep, "invalid itxn_field FirstValid")
}
+// TestInnerValidity logs fv and lv fields that are handled oddly (valid
+// rounds are copied) so we can check if they are correct.
+func TestInnerValidity(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ ep, tx, ledger := MakeSampleEnv()
+ tx.GenesisHash = crypto.Digest{0x01, 0x02, 0x03}
+ logger := TestProg(t, `
+txn FirstValid; itob; log;
+txn LastValid; itob; log;
+int 1`, AssemblerMaxVersion)
+ ledger.NewApp(tx.Receiver, 222, basics.AppParams{
+ ApprovalProgram: logger.Program,
+ })
+
+ ledger.NewAccount(appAddr(888), 50_000)
+ tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
+ TestApp(t, `
+itxn_begin
+int appl; itxn_field TypeEnum
+int 222; itxn_field ApplicationID
+itxn_submit
+itxn Logs 0; btoi; txn FirstValid; ==; assert
+itxn Logs 1; btoi; txn LastValid; ==; assert
+itxn FirstValid; txn FirstValid; ==; assert
+itxn LastValid; txn LastValid; ==; assert
+int 1
+`, ep)
+
+}
+
func TestNumInnerShallow(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go
index 436643e4c..d25851be0 100644
--- a/data/transactions/logic/evalStateful_test.go
+++ b/data/transactions/logic/evalStateful_test.go
@@ -3435,6 +3435,14 @@ func TestLatestTimestamp(t *testing.T) {
testApp(t, source, ep)
}
+func TestGenHash(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ ep, _, _ := makeSampleEnv()
+ source := fmt.Sprintf("global GenesisHash; byte 0x%s; ==", hex.EncodeToString(testGenHash[:]))
+ testApp(t, source, ep)
+}
+
func TestBlockSeed(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
@@ -3465,7 +3473,7 @@ func TestBlockSeed(t *testing.T) {
testApp(t, "int 4294967310; int 1502; -; block BlkSeed; len; int 32; ==", ep,
"not available") // 1501 back from lv is not
- // A little silly, as it only tests the test ledger: ensure samenes and differentness
+ // A little silly, as it only tests the test ledger: ensure sameness and differentness
testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff0; block BlkSeed; ==", ep)
testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff1; block BlkSeed; !=", ep)
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index fd8b9ff80..05e578601 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -1232,6 +1232,11 @@ const globalV9TestProgram = globalV8TestProgram + `
const globalV10TestProgram = globalV9TestProgram + `
global AssetCreateMinBalance; int 1001; ==; &&
global AssetOptInMinBalance; int 1001; ==; &&
+global GenesisHash; len; int 32; ==; &&
+`
+
+const globalV11TestProgram = globalV10TestProgram + `
+// No new globals in v11
`
func TestGlobal(t *testing.T) {
@@ -1254,12 +1259,17 @@ func TestGlobal(t *testing.T) {
7: {CallerApplicationAddress, globalV7TestProgram},
8: {CallerApplicationAddress, globalV8TestProgram},
9: {CallerApplicationAddress, globalV9TestProgram},
- 10: {AssetOptInMinBalance, globalV10TestProgram},
+ 10: {GenesisHash, globalV10TestProgram},
+ 11: {GenesisHash, globalV11TestProgram},
}
// tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version
require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1)
require.Len(t, globalFieldSpecs, int(invalidGlobalField))
+ // ensure we are testing everything
+ require.Equal(t, tests[AssemblerMaxVersion].lastField, invalidGlobalField-1,
+ "did you add a new global field?")
+
ledger := NewLedger(nil)
addr, err := basics.UnmarshalChecksumAddress(testAddr)
require.NoError(t, err)
@@ -1762,6 +1772,11 @@ assert
int 1
`
+const testTxnProgramTextV11 = testTxnProgramTextV10 + `
+assert
+int 1
+`
+
func makeSampleTxn() transactions.SignedTxn {
var txn transactions.SignedTxn
copy(txn.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
@@ -1865,17 +1880,17 @@ func TestTxn(t *testing.T) {
t.Parallel()
tests := map[uint64]string{
- 1: testTxnProgramTextV1,
- 2: testTxnProgramTextV2,
- 3: testTxnProgramTextV3,
- 4: testTxnProgramTextV4,
- 5: testTxnProgramTextV5,
- 6: testTxnProgramTextV6,
- 7: testTxnProgramTextV7,
- 8: testTxnProgramTextV8,
- 9: testTxnProgramTextV9,
-
+ 1: testTxnProgramTextV1,
+ 2: testTxnProgramTextV2,
+ 3: testTxnProgramTextV3,
+ 4: testTxnProgramTextV4,
+ 5: testTxnProgramTextV5,
+ 6: testTxnProgramTextV6,
+ 7: testTxnProgramTextV7,
+ 8: testTxnProgramTextV8,
+ 9: testTxnProgramTextV9,
10: testTxnProgramTextV10,
+ 11: testTxnProgramTextV11,
}
for i, txnField := range TxnFieldNames {
diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go
index bb5c2179e..060516d65 100644
--- a/data/transactions/logic/fields.go
+++ b/data/transactions/logic/fields.go
@@ -535,6 +535,9 @@ const (
// AssetOptInMinBalance is the additional minimum balance required to opt in to an asset
AssetOptInMinBalance
+ // GenesisHash is the genesis hash for the network
+ GenesisHash
+
invalidGlobalField // compile-time constant for number of fields
)
@@ -599,6 +602,7 @@ var globalFieldSpecs = [...]globalFieldSpec{
"The additional minimum balance required to create (and opt-in to) an asset."},
{AssetOptInMinBalance, StackUint64, modeAny, 10,
"The additional minimum balance required to opt-in to an asset."},
+ {GenesisHash, StackBytes32, modeAny, 10, "The Genesis Hash for the network."},
}
func globalFieldSpecByField(f GlobalField) (globalFieldSpec, bool) {
@@ -961,6 +965,7 @@ const (
BlkSeed BlockField = iota
// BlkTimestamp is the Block's timestamp, seconds from epoch
BlkTimestamp
+
invalidBlockField // compile-time constant for number of fields
)
diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go
index 7d3c2b42f..37bfeb9bc 100644
--- a/data/transactions/logic/fields_string.go
+++ b/data/transactions/logic/fields_string.go
@@ -110,12 +110,13 @@ func _() {
_ = x[CallerApplicationAddress-14]
_ = x[AssetCreateMinBalance-15]
_ = x[AssetOptInMinBalance-16]
- _ = x[invalidGlobalField-17]
+ _ = x[GenesisHash-17]
+ _ = x[invalidGlobalField-18]
}
-const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDOpcodeBudgetCallerApplicationIDCallerApplicationAddressAssetCreateMinBalanceAssetOptInMinBalanceinvalidGlobalField"
+const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDOpcodeBudgetCallerApplicationIDCallerApplicationAddressAssetCreateMinBalanceAssetOptInMinBalanceGenesisHashinvalidGlobalField"
-var _GlobalField_index = [...]uint16{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 162, 181, 205, 226, 246, 264}
+var _GlobalField_index = [...]uint16{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 162, 181, 205, 226, 246, 257, 275}
func (i GlobalField) String() string {
if i >= GlobalField(len(_GlobalField_index)-1) {
diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go
index f4736f621..2fd432956 100644
--- a/data/transactions/logic/fields_test.go
+++ b/data/transactions/logic/fields_test.go
@@ -112,6 +112,19 @@ func TestTxnFieldVersions(t *testing.T) {
asmError = "...txna opcode was introduced in ..."
txnaMode = true
}
+
+ // tack on a type check, and return a value (`int` gets compiled
+ // differently in different versions, so use `txn FirstValid` to get
+ // a positive integer)
+ switch fs.ftype.AVMType {
+ case avmUint64: // ensure the return type is uint64 by using !
+ text += "; !; pop; txn FirstValid"
+ case avmBytes: // ensure the return type is bytes by using len
+ text += "; len; pop; txn FirstValid"
+ case avmAny:
+ text += "; pop; txn FirstValid"
+ }
+
// check assembler fails if version before introduction
testLine(t, text, assemblerNoVersion, asmError)
for v := uint64(0); v < fs.version; v++ {
@@ -124,6 +137,18 @@ func TestTxnFieldVersions(t *testing.T) {
ops := testProg(t, text, AssemblerMaxVersion)
+ // check success in AssemblerMaxVersion, fs.version
+ // also ensures the field returns the right type
+ if !fs.effects {
+ txgroup[0].Txn.ApprovalProgram = []byte("approve") // not in standard sample txn
+ txgroup[0].Txn.ClearStateProgram = []byte("clear")
+ ep := defaultAppParamsWithVersion(AssemblerMaxVersion, txgroup...)
+ testAppBytes(t, ops.Program, ep)
+ opsv := testProg(t, text, fs.version)
+ ep = defaultAppParamsWithVersion(fs.version, txgroup...)
+ testAppBytes(t, opsv.Program, ep)
+ }
+
preVersion := fs.version - 1
ep := defaultSigParamsWithVersion(preVersion, txgroup...)
@@ -283,3 +308,36 @@ func TestAcctParamsFieldsVersions(t *testing.T) {
}
}
+
+func TestBlockFieldsVersions(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ for _, field := range blockFieldSpecs {
+ text := fmt.Sprintf("txn FirstValid; int 1; - ; block %s;", field.field)
+ if field.ftype.AVMType == avmBytes {
+ text += "global ZeroAddress; concat; len" // use concat to prove we have bytes
+ } else {
+ text += "global ZeroAddress; len; +" // use + to prove we have an int
+ }
+
+ testLogicRange(t, 4, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) {
+ v := ep.Proto.LogicSigVersion
+ if field.version > v {
+ // check assembler fails if version before introduction
+ testProg(t, text, v, exp(1, "...was introduced in..."))
+ ops := testProg(t, text, field.version) // assemble in the future
+ ops.Program[0] = byte(v) // but set version back to before intro
+ if v < randomnessVersion {
+ testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
+ } else {
+ testAppBytes(t, ops.Program, ep, "invalid block field")
+ }
+ } else {
+ testProg(t, text, v)
+ testApp(t, text, ep)
+ }
+ })
+
+ }
+}
diff --git a/data/transactions/logic/langspec_v1.json b/data/transactions/logic/langspec_v1.json
index b659e662a..6839e4092 100644
--- a/data/transactions/logic/langspec_v1.json
+++ b/data/transactions/logic/langspec_v1.json
@@ -1,6 +1,6 @@
{
"Version": 1,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json
index 7ee54fb98..d971b2715 100644
--- a/data/transactions/logic/langspec_v10.json
+++ b/data/transactions/logic/langspec_v10.json
@@ -1,6 +1,6 @@
{
"Version": 10,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
@@ -1196,7 +1196,8 @@
"CallerApplicationID",
"CallerApplicationAddress",
"AssetCreateMinBalance",
- "AssetOptInMinBalance"
+ "AssetOptInMinBalance",
+ "GenesisHash"
],
"ArgEnumTypes": [
"uint64",
@@ -1215,7 +1216,8 @@
"uint64",
"address",
"uint64",
- "uint64"
+ "uint64",
+ "[32]byte"
],
"DocCost": "1",
"Doc": "global field F",
@@ -4242,7 +4244,7 @@
],
"Size": 1,
"DocCost": "1",
- "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1",
+ "Doc": "create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1",
"DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.",
"IntroducedVersion": 8,
"Groups": [
@@ -4645,6 +4647,39 @@
]
},
{
+ "Opcode": 210,
+ "Name": "box_splice",
+ "Args": [
+ "boxName",
+ "uint64",
+ "uint64",
+ "[]byte"
+ ],
+ "Size": 1,
+ "DocCost": "1",
+ "Doc": "set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C.",
+ "DocExtra": "Boxes are of constant length. If C \u003c len(D), then len(D)-C bytes will be removed from the end. If C \u003e len(D), zero bytes will be appended to the end to reach the box length.",
+ "IntroducedVersion": 10,
+ "Groups": [
+ "Box Access"
+ ]
+ },
+ {
+ "Opcode": 211,
+ "Name": "box_resize",
+ "Args": [
+ "boxName",
+ "uint64"
+ ],
+ "Size": 1,
+ "DocCost": "1",
+ "Doc": "change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768.",
+ "IntroducedVersion": 10,
+ "Groups": [
+ "Box Access"
+ ]
+ },
+ {
"Opcode": 224,
"Name": "ec_add",
"Args": [
diff --git a/data/transactions/logic/langspec_v2.json b/data/transactions/logic/langspec_v2.json
index b518b1a42..a832f8664 100644
--- a/data/transactions/logic/langspec_v2.json
+++ b/data/transactions/logic/langspec_v2.json
@@ -1,6 +1,6 @@
{
"Version": 2,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
diff --git a/data/transactions/logic/langspec_v3.json b/data/transactions/logic/langspec_v3.json
index a8bc6df0d..f1566b528 100644
--- a/data/transactions/logic/langspec_v3.json
+++ b/data/transactions/logic/langspec_v3.json
@@ -1,6 +1,6 @@
{
"Version": 3,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
diff --git a/data/transactions/logic/langspec_v4.json b/data/transactions/logic/langspec_v4.json
index ffc428822..49591c10f 100644
--- a/data/transactions/logic/langspec_v4.json
+++ b/data/transactions/logic/langspec_v4.json
@@ -1,6 +1,6 @@
{
"Version": 4,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
diff --git a/data/transactions/logic/langspec_v5.json b/data/transactions/logic/langspec_v5.json
index 2b946629c..ccde5509f 100644
--- a/data/transactions/logic/langspec_v5.json
+++ b/data/transactions/logic/langspec_v5.json
@@ -1,6 +1,6 @@
{
"Version": 5,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
diff --git a/data/transactions/logic/langspec_v6.json b/data/transactions/logic/langspec_v6.json
index 20575dae5..72e1d9c64 100644
--- a/data/transactions/logic/langspec_v6.json
+++ b/data/transactions/logic/langspec_v6.json
@@ -1,6 +1,6 @@
{
"Version": 6,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
diff --git a/data/transactions/logic/langspec_v7.json b/data/transactions/logic/langspec_v7.json
index d8be33960..7229ee534 100644
--- a/data/transactions/logic/langspec_v7.json
+++ b/data/transactions/logic/langspec_v7.json
@@ -1,6 +1,6 @@
{
"Version": 7,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json
index 3b496ddcb..2f1576864 100644
--- a/data/transactions/logic/langspec_v8.json
+++ b/data/transactions/logic/langspec_v8.json
@@ -1,6 +1,6 @@
{
"Version": 8,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
@@ -4238,7 +4238,7 @@
],
"Size": 1,
"DocCost": "1",
- "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1",
+ "Doc": "create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1",
"DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.",
"IntroducedVersion": 8,
"Groups": [
diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json
index c52d36862..3ec2d39af 100644
--- a/data/transactions/logic/langspec_v9.json
+++ b/data/transactions/logic/langspec_v9.json
@@ -1,6 +1,6 @@
{
"Version": 9,
- "LogicSigVersion": 9,
+ "LogicSigVersion": 10,
"NamedTypes": [
{
"Name": "[32]byte",
@@ -4238,7 +4238,7 @@
],
"Size": 1,
"DocCost": "1",
- "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1",
+ "Doc": "create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1",
"DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.",
"IntroducedVersion": 8,
"Groups": [
diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go
index a111eec13..a575c493d 100644
--- a/data/transactions/logic/ledger_test.go
+++ b/data/transactions/logic/ledger_test.go
@@ -36,6 +36,7 @@ import (
"math"
"math/rand"
+ "github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/committee"
@@ -607,6 +608,13 @@ func (l *Ledger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Addr
return basics.AppParams{}, basics.Address{}, fmt.Errorf("no app %d", appID)
}
+var testGenHash = crypto.Digest{0x03, 0x02, 0x03}
+
+// GenesisHash returns a phony genesis hash that can be tested against
+func (l *Ledger) GenesisHash() crypto.Digest {
+ return testGenHash
+}
+
func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) error {
fbr, ok := l.balances[from]
if !ok {
diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go
index 37f8ba790..e67907ea4 100644
--- a/data/transactions/logic/mocktracer/scenarios.go
+++ b/data/transactions/logic/mocktracer/scenarios.go
@@ -379,7 +379,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, false),
{
- AfterProgram(logic.ModeApp, false),
+ AfterProgram(logic.ModeApp, ProgramResultPass),
AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, false),
AfterTxnGroup(1, nil, false), // end first itxn group
AfterOpcode(false),
@@ -397,7 +397,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, false),
{
- AfterProgram(logic.ModeApp, false),
+ AfterProgram(logic.ModeApp, ProgramResultPass),
AfterTxn(protocol.ApplicationCallTx, expectedAD, false),
},
}),
@@ -420,6 +420,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
failureInnerProgramBytes := []byte{0x06, 0x80, 0x01, 0x78, 0xb0, 0x81, 0x00} // #pragma version 6; pushbytes "x"; log; pushint 0
failureMessage := "transaction rejected by ApprovalProgram"
outcome := RejectionOutcome
+ programFailingResult := ProgramResultReject
if shouldError {
// We could use just the err opcode here, but we want to use two opcodes to maintain
// trace event consistency with rejections.
@@ -428,6 +429,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
failureInnerProgramBytes = []byte{0x06, 0x80, 0x01, 0x78, 0xb0, 0x00} // #pragma version 6; pushbytes "x"; log; err
failureMessage = "err opcode executed"
outcome = ErrorOutcome
+ programFailingResult = ProgramResultError
}
failureInnerProgram := "0x" + hex.EncodeToString(failureInnerProgramBytes)
@@ -458,7 +460,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(4, shouldError),
{
- AfterProgram(logic.ModeApp, shouldError),
+ AfterProgram(logic.ModeApp, programFailingResult),
AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true),
},
}),
@@ -510,11 +512,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, shouldError),
{
- AfterProgram(logic.ModeApp, shouldError),
+ AfterProgram(logic.ModeApp, programFailingResult),
AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, true),
AfterTxnGroup(1, nil, true), // end first itxn group
AfterOpcode(true),
- AfterProgram(logic.ModeApp, true),
+ AfterProgram(logic.ModeApp, ProgramResultError),
AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true),
},
}),
@@ -565,14 +567,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, false),
{
- AfterProgram(logic.ModeApp, false),
+ AfterProgram(logic.ModeApp, ProgramResultPass),
AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false),
AfterTxnGroup(1, nil, false), // end first itxn group
AfterOpcode(false),
},
OpcodeEvents(4, shouldError),
{
- AfterProgram(logic.ModeApp, shouldError),
+ AfterProgram(logic.ModeApp, programFailingResult),
AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true),
},
}),
@@ -625,7 +627,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, false),
{
- AfterProgram(logic.ModeApp, false),
+ AfterProgram(logic.ModeApp, ProgramResultPass),
AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false),
AfterTxnGroup(1, nil, false), // end first itxn group
AfterOpcode(false),
@@ -638,7 +640,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, true),
AfterTxnGroup(2, nil, true), // end second itxn group
AfterOpcode(true),
- AfterProgram(logic.ModeApp, true),
+ AfterProgram(logic.ModeApp, ProgramResultError),
AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true),
},
}),
@@ -691,7 +693,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, false),
{
- AfterProgram(logic.ModeApp, false),
+ AfterProgram(logic.ModeApp, ProgramResultPass),
AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false),
AfterTxnGroup(1, nil, false), // end first itxn group
AfterOpcode(false),
@@ -706,7 +708,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, true),
AfterTxnGroup(2, nil, true), // end second itxn group
AfterOpcode(true),
- AfterProgram(logic.ModeApp, true),
+ AfterProgram(logic.ModeApp, ProgramResultError),
AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true),
},
}),
@@ -754,7 +756,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, false),
{
- AfterProgram(logic.ModeApp, false),
+ AfterProgram(logic.ModeApp, ProgramResultPass),
AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false),
AfterTxnGroup(1, nil, false), // end first itxn group
AfterOpcode(false),
@@ -772,7 +774,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator {
},
OpcodeEvents(3, shouldError),
{
- AfterProgram(logic.ModeApp, shouldError),
+ AfterProgram(logic.ModeApp, programFailingResult),
AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true),
},
}),
diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go
index 13a6d92d7..f40faf1be 100644
--- a/data/transactions/logic/mocktracer/tracer.go
+++ b/data/transactions/logic/mocktracer/tracer.go
@@ -74,6 +74,9 @@ type Event struct {
// only for BeforeTxnGroup and AfterTxnGroup
GroupSize int
+ // only for AfterProgram
+ Pass bool
+
// only for AfterOpcode, AfterProgram, AfterTxn, and AfterTxnGroup
HasError bool
@@ -111,9 +114,21 @@ func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData, hasError bool)
return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad, HasError: hasError}
}
+// ProgramResult represents the result of a program execution
+type ProgramResult int
+
+const (
+ // ProgramResultPass represents a program that passed
+ ProgramResultPass ProgramResult = iota
+ // ProgramResultReject represents a program that rejected
+ ProgramResultReject
+ // ProgramResultError represents a program that errored
+ ProgramResultError
+)
+
// AfterProgram creates a new Event with the type AfterProgramEvent
-func AfterProgram(mode logic.RunMode, hasError bool) Event {
- return Event{Type: AfterProgramEvent, LogicEvalMode: mode, HasError: hasError}
+func AfterProgram(mode logic.RunMode, result ProgramResult) Event {
+ return Event{Type: AfterProgramEvent, LogicEvalMode: mode, Pass: result == ProgramResultPass, HasError: result == ProgramResultError}
}
// BeforeOpcode creates a new Event with the type BeforeOpcodeEvent
@@ -189,8 +204,17 @@ func (d *Tracer) BeforeProgram(cx *logic.EvalContext) {
}
// AfterProgram mocks the logic.EvalTracer.AfterProgram method
-func (d *Tracer) AfterProgram(cx *logic.EvalContext, evalError error) {
- d.Events = append(d.Events, AfterProgram(cx.RunMode(), evalError != nil))
+func (d *Tracer) AfterProgram(cx *logic.EvalContext, pass bool, evalError error) {
+ var result ProgramResult
+ if pass {
+ result = ProgramResultPass
+ } else if evalError != nil {
+ result = ProgramResultError
+ } else {
+ result = ProgramResultReject
+
+ }
+ d.Events = append(d.Events, AfterProgram(cx.RunMode(), result))
}
// BeforeOpcode mocks the logic.EvalTracer.BeforeOpcode method
diff --git a/data/transactions/logic/opcodeExplain.go b/data/transactions/logic/opcodeExplain.go
index 4b4f965a6..3643ade21 100644
--- a/data/transactions/logic/opcodeExplain.go
+++ b/data/transactions/logic/opcodeExplain.go
@@ -197,6 +197,12 @@ func opBoxReplaceStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, bas
return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[pprev].Bytes)
}
+func opBoxSpliceStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) {
+ name := len(cx.Stack) - 4 // name, start, length, replacement
+
+ return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[name].Bytes)
+}
+
func opBoxDelStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) {
last := len(cx.Stack) - 1 // name
@@ -210,6 +216,12 @@ func opBoxPutStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.
return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[prev].Bytes)
}
+func opBoxResizeStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) {
+ name := len(cx.Stack) - 2 // name, size
+
+ return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[name].Bytes)
+}
+
func opAppLocalGetStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) {
last := len(cx.Stack) - 1 // state key
prev := last - 1 // account
diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go
index dc2d44bc0..1f595dfb7 100644
--- a/data/transactions/logic/opcodes.go
+++ b/data/transactions/logic/opcodes.go
@@ -27,7 +27,7 @@ import (
)
// LogicVersion defines default assembler and max eval versions
-const LogicVersion = 10
+const LogicVersion = 11
// rekeyingEnabledVersion is the version of TEAL where RekeyTo functionality
// was enabled. This is important to remember so that old TEAL accounts cannot
@@ -75,6 +75,7 @@ const sharedResourcesVersion = 9 // apps can access resources from other transac
// moved from vFuture to a new consensus version. If they remain unready, bump
// their version, and fixup TestAssemble() in assembler_test.go.
const pairingVersion = 10 // bn256 opcodes. will add bls12-381, and unify the available opcodes.
+const spliceVersion = 10 // box splicing/resizing
// Unlimited Global Storage opcodes
const boxVersion = 8 // box_*
@@ -721,6 +722,8 @@ var OpSpecs = []OpSpec{
// randomness support
{0xd0, "vrf_verify", opVrfVerify, proto("b83:bT"), randomnessVersion, field("s", &VrfStandards).costs(5700)},
{0xd1, "block", opBlock, proto("i:a"), randomnessVersion, field("f", &BlockFields)},
+ {0xd2, "box_splice", opBoxSplice, proto("Niib:").appStateExplain(opBoxSpliceStateChange), spliceVersion, only(ModeApp)},
+ {0xd3, "box_resize", opBoxResize, proto("Ni:").appStateExplain(opBoxResizeStateChange), spliceVersion, only(ModeApp)},
{0xe0, "ec_add", opEcAdd, proto("bb:b"), pairingVersion,
costByField("g", &EcGroups, []int{
diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json
index 56cf7b5dc..e414922e8 100644
--- a/data/transactions/logic/teal.tmLanguage.json
+++ b/data/transactions/logic/teal.tmLanguage.json
@@ -72,7 +72,7 @@
},
{
"name": "keyword.other.unit.teal",
- "match": "^(box_create|box_del|box_extract|box_get|box_len|box_put|box_replace|acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|block|log|min_balance)\\b"
+ "match": "^(box_create|box_del|box_extract|box_get|box_len|box_put|box_replace|box_resize|box_splice|acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|block|log|min_balance)\\b"
},
{
"name": "keyword.operator.teal",
@@ -112,7 +112,7 @@
},
{
"name": "variable.parameter.teal",
- "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|VrfAlgorand|BlkSeed|BlkTimestamp|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2)\\b"
+ "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|GenesisHash|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|VrfAlgorand|BlkSeed|BlkTimestamp|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2)\\b"
}
]
},
diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go
index 5894409ba..37603c794 100644
--- a/data/transactions/logic/tracer.go
+++ b/data/transactions/logic/tracer.go
@@ -151,7 +151,7 @@ type EvalTracer interface {
BeforeProgram(cx *EvalContext)
// AfterProgram is called after an app or LogicSig program is evaluated.
- AfterProgram(cx *EvalContext, evalError error)
+ AfterProgram(cx *EvalContext, pass bool, evalError error)
// BeforeOpcode is called before the op is evaluated
BeforeOpcode(cx *EvalContext)
@@ -188,7 +188,7 @@ func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions
func (n NullEvalTracer) BeforeProgram(cx *EvalContext) {}
// AfterProgram does nothing
-func (n NullEvalTracer) AfterProgram(cx *EvalContext, evalError error) {}
+func (n NullEvalTracer) AfterProgram(cx *EvalContext, pass bool, evalError error) {}
// BeforeOpcode does nothing
func (n NullEvalTracer) BeforeOpcode(cx *EvalContext) {}
diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go
index 5d44cbe2f..323447464 100644
--- a/data/transactions/logic/tracer_test.go
+++ b/data/transactions/logic/tracer_test.go
@@ -44,7 +44,7 @@ func getSimpleTracerTestCases(mode RunMode) []tracerTestCase {
},
mocktracer.OpcodeEvents(35, false),
{
- mocktracer.AfterProgram(mode, false),
+ mocktracer.AfterProgram(mode, mocktracer.ProgramResultPass),
},
}),
},
@@ -58,7 +58,7 @@ func getSimpleTracerTestCases(mode RunMode) []tracerTestCase {
},
mocktracer.OpcodeEvents(36, false),
{
- mocktracer.AfterProgram(mode, false),
+ mocktracer.AfterProgram(mode, mocktracer.ProgramResultReject),
},
}),
},
@@ -72,7 +72,7 @@ func getSimpleTracerTestCases(mode RunMode) []tracerTestCase {
},
mocktracer.OpcodeEvents(36, true),
{
- mocktracer.AfterProgram(mode, true),
+ mocktracer.AfterProgram(mode, mocktracer.ProgramResultError),
},
}),
},
@@ -90,7 +90,7 @@ func getPanicTracerTestCase(mode RunMode) tracerTestCase {
},
mocktracer.OpcodeEvents(36, true),
{
- mocktracer.AfterProgram(mode, true),
+ mocktracer.AfterProgram(mode, mocktracer.ProgramResultError),
},
}),
}
diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go
index 416c0e4c0..1e7793248 100644
--- a/data/transactions/verify/txn_test.go
+++ b/data/transactions/verify/txn_test.go
@@ -78,6 +78,9 @@ func (d *DummyLedgerForSignature) BlockHdr(rnd basics.Round) (blk bookkeeping.Bl
}
return createDummyBlockHeader(), nil
}
+func (d *DummyLedgerForSignature) GenesisHash() crypto.Digest {
+ return crypto.Digest{}
+}
func (d *DummyLedgerForSignature) Latest() basics.Round {
return 0
}
@@ -383,13 +386,13 @@ pushint 1`,
{
mocktracer.BeforeProgram(logic.ModeSig), // first txn start
mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op
- mocktracer.AfterProgram(logic.ModeSig, false), // first txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // first txn end
// nothing for second txn (not signed with a LogicSig)
mocktracer.BeforeProgram(logic.ModeSig), // third txn start
},
mocktracer.OpcodeEvents(3, false), // third txn LogicSig: 3 ops
{
- mocktracer.AfterProgram(logic.ModeSig, false), // third txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // third txn end
},
}),
},
@@ -406,13 +409,13 @@ pushint 0`,
{
mocktracer.BeforeProgram(logic.ModeSig), // first txn start
mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op
- mocktracer.AfterProgram(logic.ModeSig, false), // first txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // first txn end
// nothing for second txn (not signed with a LogicSig)
mocktracer.BeforeProgram(logic.ModeSig), // third txn start
},
mocktracer.OpcodeEvents(3, false), // third txn LogicSig: 3 ops
{
- mocktracer.AfterProgram(logic.ModeSig, false), // third txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultReject), // third txn end
},
}),
},
@@ -431,13 +434,13 @@ pop`,
{
mocktracer.BeforeProgram(logic.ModeSig), // first txn start
mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op
- mocktracer.AfterProgram(logic.ModeSig, false), // first txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // first txn end
// nothing for second txn (not signed with a LogicSig)
mocktracer.BeforeProgram(logic.ModeSig), // third txn start
},
mocktracer.OpcodeEvents(3, true), // third txn LogicSig: 3 ops
{
- mocktracer.AfterProgram(logic.ModeSig, true), // third txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultError), // third txn end
},
}),
},
@@ -453,7 +456,7 @@ pushint 1`,
expectedEvents: []mocktracer.Event{
mocktracer.BeforeProgram(logic.ModeSig), // first txn start
mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op
- mocktracer.AfterProgram(logic.ModeSig, false), // first txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultReject), // first txn end
// execution stops at rejection
},
},
@@ -469,7 +472,7 @@ pushint 1`,
expectedEvents: []mocktracer.Event{
mocktracer.BeforeProgram(logic.ModeSig), // first txn start
mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(true), // first txn LogicSig: 1 op
- mocktracer.AfterProgram(logic.ModeSig, true), // first txn end
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultError), // first txn end
// execution stops at error
},
},
diff --git a/docker/build/aptly.Dockerfile b/docker/build/aptly.Dockerfile
index 1849d8e5e..24982e741 100644
--- a/docker/build/aptly.Dockerfile
+++ b/docker/build/aptly.Dockerfile
@@ -1,26 +1,16 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
ARG ARCH=amd64
-ARG GOLANG_VERSION
-ENV DEBIAN_FRONTEND noninteractive
-RUN apt-get update && apt-get install aptly awscli binutils build-essential curl gnupg2 -y
-RUN curl https://dl.google.com/go/go${GOLANG_VERSION}.linux-${ARCH%v*}.tar.gz | tar -xzf - && mv go /usr/local
-ENV GOROOT=/usr/local/go \
- GOPATH=/root/go \
- PATH=$GOPATH/bin:$GOROOT/bin:$PATH
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install aptly awscli binutils build-essential curl gnupg2 -y
WORKDIR /root
COPY .aptly.conf .
-RUN curl https://releases.algorand.com/key.pub | gpg --no-default-keyring --keyring trustedkeys.gpg --import - && \
- aptly mirror create stable https://releases.algorand.com/deb/ stable main && \
+RUN curl https://releases.algorand.com/key.pub | gpg --no-default-keyring --keyring /root/.gnupg/trustedkeys.gpg --import -
+RUN gpg --no-default-keyring --keyring /root/.gnupg/trustedkeys.gpg --export --output /root/.gnupg/newkeyring.gpg && mv -f /root/.gnupg/newkeyring.gpg /root/.gnupg/trustedkeys.gpg
+RUN aptly mirror create stable https://releases.algorand.com/deb/ stable main && \
aptly mirror create beta https://releases.algorand.com/deb/ beta main && \
- aptly repo create -distribution=stable -architectures=amd64 -component=main -comment=mainnet stable && \
- aptly repo create -distribution=beta -architectures=amd64 -component=main -comment=betanet beta && \
- aptly mirror update stable && \
- aptly mirror update beta && \
- aptly repo import stable stable algorand algorand-devtools && \
- aptly repo import beta beta algorand-beta algorand-devtools-beta
+ aptly repo create -distribution=stable -architectures=amd64,arm64 -component=main -comment=mainnet stable && \
+ aptly repo create -distribution=beta -architectures=amd64,arm64 -component=main -comment=betanet beta
CMD ["/bin/bash"]
-
diff --git a/docker/build/docker.ubuntu.Dockerfile b/docker/build/docker.ubuntu.Dockerfile
index 5091afefa..e82dba3d7 100644
--- a/docker/build/docker.ubuntu.Dockerfile
+++ b/docker/build/docker.ubuntu.Dockerfile
@@ -3,18 +3,14 @@ ARG ARCH="amd64"
FROM ${ARCH}/ubuntu:20.04
ARG GOLANG_VERSION
ARG ARCH="amd64"
-RUN apt-get update && apt-get install curl python python3.7 python3-pip build-essential apt-transport-https ca-certificates software-properties-common -y && \
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install curl python python3.7 python3-pip build-essential apt-transport-https ca-certificates software-properties-common -y && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \
- add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \
- apt-get update && apt-get install docker-ce -y
+ DEBIAN_FRONTEND=noninteractive add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \
+ apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install docker-ce -y
-# Mule needs >= python3.7 so set that as the default.
-RUN update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 && \
- update-alternatives --install /usr/bin/python python /usr/bin/python3.7 2 && \
- update-alternatives --set python /usr/bin/python3.7 && \
- pip3 install mulecli
+RUN pip3 install mulecli
-RUN apt-get update && apt-get install -y autoconf bsdmainutils git && \
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y autoconf bsdmainutils git && \
curl https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz | tar -xzf - && \
mv go /usr/local
diff --git a/docs/participation_key_lifecycle.md b/docs/participation_key_lifecycle.md
new file mode 100644
index 000000000..6609d5f4b
--- /dev/null
+++ b/docs/participation_key_lifecycle.md
@@ -0,0 +1,210 @@
+# Participation Key Lifecycle
+
+This document goes into technical details about the lifecycle of participation
+keys. Before getting into that let's briefly discuss the purpose of these keys.
+
+## Overview
+Participation keys are used to participate in the consensus protocol. Aside
+from the registration process, they are completely decoupled from the account
+secret key.
+
+Each set of participation keys includes voting keys for each and every round
+for their validity range, in addition to state proof secrets for each and every
+state proof round (a state proof is not created every round). Because there
+are separate keys for each action we sometimes refer to the voting keys as
+**OneTimeKeys**. After each key is used, the secret is permanently deleted from
+the node. This is a security property that eliminates the possibility of votes
+being changed in the event a node becomes compromised at a later date. This
+helps ensure finality even if a serious security breach were to occur.
+
+Aside from the **Registration** event, keys are completely decoupled from the
+account secrets. This is another security property. A securely managed account
+need only be exposed when signing the key registration transaction. For this
+reason tools like **algokey** are available for fully offline transaction
+signing.
+
+Given a set of participation keys, and a signed key registration transaction,
+an algod node can be configured to participate in the consensus protocol.
+For each round a single-use key is used to verify the authenticity of messages.
+
+## Key Creation
+
+Keys can be created with **goal** or **algokey**. There are a number of
+shortcuts and helpers, especially in **goal**, which won't be covered here.
+
+Required parameters are as follows:
+* **First**: the first round the keys will be valid for.
+* **Last**: the last round the keys will be valid for.
+* **Parent**: The public address of the account that will register the keys.
+
+The parent is stored along with the keys for convenience and error checking.
+It is not part of the cryptography. It can technically be set later, but must
+be set before the keys can be installed or registered.
+
+There is one optional parameter:
+* **Dilution**: Configure the multi-level key storage.
+
+Internally, keys are stored as a two-level hierarchy of ed25519 keys.
+
+```mermaid
+flowchart TD
+ root["onetime signature container"] --> p0["Batch Signer 1"]
+ root --> p1["Batch Signer 2"]
+ root --> pDot["..."]
+ root --> pN["Batch Signer N"]
+ p0 --> v0["Batch 1 Key 1"]
+ p0 --> v1["Batch 1 Key 2"]
+ p0 --> vDot["..."]
+ p0 --> vN["Batch 1 Key N"]
+```
+
+The top-level array of keys are used to generate the actual voting keys.
+This allows them to be significantly smaller, because voting keys are not
+generated until they are required. The **Dilution** parameter defines how many
+voting keys are generated for each top-level key. This is done for space
+efficiency. It is optional because **sqrt(Last - First)** gives us the most
+space efficient value.
+
+Using **algokey** a set of keys can be generated with the command:
+```
+algokey part generate --first 35000000 --last 36000000 --parent <account-address> --keyfile keys.db
+```
+
+This creates a SQLite DB file named **keys.db**. The schema is pretty basic,
+consisting of BLOBs for voting keys. State proof keys are also included and are
+a bit more involved in their storage pattern.
+
+Similar functionality is built into **goal** along with convenience methods to:
+* Generate and install.
+* Generate, install and register.
+* Lookup account, regenerate, install and register.
+* Maybe others, we got carried away.
+
+## Key Installation
+
+The node can only use keys which have been installed, so files like **keys.db**
+generated above must be provided to the node. Technically, it's possible for
+an account to register a set of keys which are not installed, but this is very
+bad. It means the stake calculations are accounting for an account which does
+not have a properly configured node.
+
+In older versions of **algod**, keys were installed by dropping the database
+file into the data directory. This caused frequent errors with many deployments
+because the data directory is often owned by a restricted user and copying the
+files around could easily lead to permission errors.
+
+The current version of **algod** supports a series of endpoints on the Admin API
+to install and manage keys. One of them is a POST for installation:
+
+```
+POST /v2/participation/
+```
+
+For example, install the keys generated above to a node located at **localhost:1234** with the following:
+```
+curl -X POST --data-binary @keys.db -H "Authorization: Bearer admin-token-here" "localhost:1234/v2/participation"
+```
+
+**goal** provides a convenience function for this operation:
+```
+goal account installpartkey --partkey keys.db --delete-input -d /path/to/data-dir
+```
+
+Keys should only be installed on a single node. In the event that multiple
+nodes have the same set of keys, multiple votes could be cast for a single
+account. This could lead to "equivocation" votes, where multiple conflicting
+votes are cast for the same account causing both to be ignored.
+
+## Key Storage
+
+Once installed keys are stored in the **Participation Registry**. This is a
+service that wraps a SQLite file for storage. Once installed, keys are assigned
+an ID, which is referred to as **<participation-ID>** below. The ID is a hash
+built from parts of the participation key metadata. There are additional Admin
+API endpoints available to manage the registry:
+
+```
+DELETE /v2/participation/<participation-id>
+GET /v2/participation/
+GET /v2/participation/<participation-id>
+```
+
+## Key Registration
+
+After installing a key, the account is still considered offline. The network
+does not know about the local keys and will not accept votes until the keys are
+registered with the network. This is done using a special key registration
+transaction.
+
+Like account creation, there are utilities in both **goal** and **algokey**.
+For this document we'll use **algokey**. Because it is designed to be used offline
+you need to provide the current round.
+
+It has the following arguments:
+* **network**: One of devnet, betanet, testnet or mainnet.
+* **firstvalid**: The first round where this transaction may be submitted.
+* **keyfile**: The keyfile generated earlier. This is used to source most parameters.
+* **outputFile**: The unsigned keyreg transaction.
+
+Here is an example call that creates **keyreg.txn**, an unsigned keyreg transaction:
+```
+algokey part keyreg --network mainnet --firstvalid 31998000 --keyfile keys.db -o keyreg.txn
+```
+
+The unsigned transaction can also be signed with **algokey**:
+```
+algokey sign -t keyreg.txn -o keyreg.stxn -m “[enter your account’s 25 word private key delimited by spaces]”
+```
+
+Now copy the signed **keyreg.stxn** file to an node and submit it as usual.
+It does not need to be the same node which was used to install the keys.
+```
+goal clerk rawsend -f keyreg.stxn -d /path/to/data-dir
+```
+
+The node monitors blocks for key registrations and updates the **Participation Registry**.
+
+## Voting
+
+The node asks the registry for keys on each round and uses them as needed. At
+the end of the round it tells the registry to delete them.
+
+## Participation Registry
+
+The participation registry is optimized in several ways to avoid slowing down
+the consensus protocol.
+
+Reads are optimized by caching all keys on startup.
+
+Writes are optimized by putting disk IO in a separate thread. Operations like
+deleting old keys (and installing, updating, etc) are performed asynchronously.
+The cache is manually updated when async operations are initiated to ensure it
+always represents the current state.
+
+# Appendix 1: Key Registration Delay and Overlapping keys
+
+When a key is registered, there is a delay of 320 rounds before it can be used.
+The number 320 is defined by the **balanceRound** function, which derives it
+from the **SeedRefreshInterval** and **SeedLookback** consensus settings.
+This delay is intended to circumvent some specific attacks related to
+registering new voting keys at a high frequency. See the research papers for
+those details, here we'll focus on some implications of this property.
+
+* When an account is brought online for the first time, it doesn't vote until
+ 320 rounds after it was registered.
+* When an account renews its voting keys by installing a new set of keys and
+ registering them, there is a 320 round window where the old keys are still
+ used. During this window you must not remove first set of keys from the node
+ or else your account will not vote properly.
+
+# Appendix 2: On-chain Storage
+
+When a key registration transaction is evaluated, public keys required to
+verify votes from that account are written to the account record. This is the
+only on-chain component for voting keys.
+
+Each participating node will accumulate votes and write them to a
+**Certificate** which serves to validate the block. The certificate would be
+validated using the public keys stored in each account. Because there are many
+nodes accumulating votes at the same time, it is possible to have multiple
+correct but different certificates validating the same block.
diff --git a/ledger/eval/appcow_test.go b/ledger/eval/appcow_test.go
index 2c46d02c0..a4c8d22c6 100644
--- a/ledger/eval/appcow_test.go
+++ b/ledger/eval/appcow_test.go
@@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
@@ -99,8 +100,8 @@ func (ml *emptyLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, erro
return bookkeeping.BlockHeader{}, nil
}
-func (ml *emptyLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) {
- return bookkeeping.BlockHeader{}, nil
+func (ml *emptyLedger) GenesisHash() crypto.Digest {
+ return crypto.Digest{}
}
func (ml *emptyLedger) GetStateProofNextRound() basics.Round {
diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go
index d797a4a56..f3e6b3d04 100644
--- a/ledger/eval/cow.go
+++ b/ledger/eval/cow.go
@@ -22,6 +22,7 @@ import (
"sync"
"github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
@@ -65,6 +66,7 @@ type roundCowParent interface {
getKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error)
kvGet(key string) ([]byte, bool, error)
GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error)
+ GenesisHash() crypto.Digest
}
// When adding new fields make sure to clear them in the roundCowState.recycle() as well to avoid dirty state
@@ -245,6 +247,10 @@ func (cb *roundCowState) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, erro
return cb.lookupParent.BlockHdr(r)
}
+func (cb *roundCowState) GenesisHash() crypto.Digest {
+ return cb.lookupParent.GenesisHash()
+}
+
func (cb *roundCowState) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) {
return cb.lookupParent.GetStateProofVerificationContext(stateProofLastAttestedRound)
}
diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go
index 225b03799..5e1253adc 100644
--- a/ledger/eval/cow_test.go
+++ b/ledger/eval/cow_test.go
@@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/stateproofmsg"
@@ -103,8 +104,8 @@ func (ml *mockLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error
return bookkeeping.BlockHeader{}, errors.New("requested blockheader not found")
}
-func (ml *mockLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) {
- return ml.BlockHdr(rnd)
+func (ml *mockLedger) GenesisHash() crypto.Digest {
+ panic("GenesisHash unused by tests")
}
func (ml *mockLedger) GetStateProofVerificationContext(rnd basics.Round) (*ledgercore.StateProofVerificationContext, error) {
diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go
index 58f7fc8a4..714e6bddf 100644
--- a/ledger/eval/eval.go
+++ b/ledger/eval/eval.go
@@ -40,6 +40,7 @@ import (
// LedgerForCowBase represents subset of Ledger functionality needed for cow business
type LedgerForCowBase interface {
BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
+ GenesisHash() crypto.Digest
CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error
LookupWithoutRewards(basics.Round, basics.Address) (ledgercore.AccountData, basics.Round, error)
LookupAsset(basics.Round, basics.Address, basics.AssetIndex) (ledgercore.AssetResource, error)
@@ -337,6 +338,10 @@ func (x *roundCowBase) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error)
return x.l.BlockHdr(r)
}
+func (x *roundCowBase) GenesisHash() crypto.Digest {
+ return x.l.GenesisHash()
+}
+
func (x *roundCowBase) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) {
return x.l.GetStateProofVerificationContext(stateProofLastAttestedRound)
}
diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go
index b91655831..cb6bf0878 100644
--- a/ledger/eval/eval_test.go
+++ b/ledger/eval/eval_test.go
@@ -543,7 +543,7 @@ int 1`,
},
mocktracer.OpcodeEvents(3, false),
{
- mocktracer.AfterProgram(logic.ModeApp, false),
+ mocktracer.AfterProgram(logic.ModeApp, mocktracer.ProgramResultPass),
mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, false), // end basicAppCallTxn
mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn
mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, false), // end payTxn
@@ -559,6 +559,11 @@ int 1`,
delete(expectedBasicAppCallDelta.Txids, txgroup[0].Txn.ID())
hasError := testCase.firstTxnBehavior == "error"
+ expectedProgramResult := mocktracer.ProgramResultReject
+ if hasError {
+ expectedProgramResult = mocktracer.ProgramResultError
+ }
+
// EvalDeltas are removed from failed app call transactions
expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{}
expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{
@@ -569,7 +574,7 @@ int 1`,
},
mocktracer.OpcodeEvents(3, hasError),
{
- mocktracer.AfterProgram(logic.ModeApp, hasError),
+ mocktracer.AfterProgram(logic.ModeApp, expectedProgramResult),
mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, true), // end basicAppCallTxn
mocktracer.AfterTxnGroup(3, &expectedBasicAppCallDelta, true),
},
@@ -1037,6 +1042,10 @@ func (l *testCowBaseLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, err
return bookkeeping.BlockHeader{}, errors.New("not implemented")
}
+func (l *testCowBaseLedger) GenesisHash() crypto.Digest {
+ panic("not implemented")
+}
+
func (l *testCowBaseLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error {
return errors.New("not implemented")
}
diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go
index 3fbe61512..7d2bf8380 100644
--- a/ledger/simulation/simulation_eval_test.go
+++ b/ledger/simulation/simulation_eval_test.go
@@ -865,6 +865,254 @@ int 0
})
}
+const returnFirstAppArgProgram = `#pragma version 6
+byte "counter"
+dup
+app_global_get
+int 1
++
+app_global_put
+
+txn ApplicationID
+bz end
+
+txn OnCompletion
+int OptIn
+==
+bnz end
+
+txn ApplicationArgs 0
+btoi
+return
+
+end:
+int 1
+return`
+
+func TestClearStateRejection(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ simulationTest(t, func(env simulationtesting.Environment) simulationTestCase {
+ sender := env.Accounts[0]
+ user := env.Accounts[1]
+
+ appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{
+ ApprovalProgram: returnFirstAppArgProgram,
+ ClearStateProgram: returnFirstAppArgProgram,
+ GlobalStateSchema: basics.StateSchema{
+ NumUint: 1,
+ },
+ })
+ env.OptIntoApp(user.Addr, appID)
+
+ clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: user.Addr,
+ ApplicationID: appID,
+ OnCompletion: transactions.ClearStateOC,
+ ApplicationArgs: [][]byte{{0}},
+ })
+ otherAppCall := env.TxnInfo.NewTxn(txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: user.Addr,
+ ApplicationID: appID,
+ ApplicationArgs: [][]byte{{1}},
+ })
+
+ txntest.Group(&clearStateTxn, &otherAppCall)
+
+ signedClearStateTxn := clearStateTxn.Txn().Sign(user.Sk)
+ signedOtherAppCall := otherAppCall.Txn().Sign(user.Sk)
+
+ return simulationTestCase{
+ input: simulation.Request{
+ TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn, signedOtherAppCall}},
+ },
+ expected: simulation.Result{
+ Version: simulation.ResultLatestVersion,
+ LastRound: env.TxnInfo.LatestRound(),
+ TxnGroups: []simulation.TxnGroupResult{
+ {
+ Txns: []simulation.TxnResult{
+ {
+ // No EvalDelta changes because the clear state failed
+ AppBudgetConsumed: 16,
+ },
+ {
+ Txn: transactions.SignedTxnWithAD{
+ ApplyData: transactions.ApplyData{
+ EvalDelta: transactions.EvalDelta{
+ GlobalDelta: basics.StateDelta{
+ "counter": {
+ Action: basics.SetUintAction,
+ Uint: 3,
+ },
+ },
+ },
+ },
+ },
+ AppBudgetConsumed: 16,
+ },
+ },
+ AppBudgetAdded: 1400,
+ AppBudgetConsumed: 32,
+ },
+ },
+ },
+ }
+ })
+}
+
+func TestClearStateError(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ simulationTest(t, func(env simulationtesting.Environment) simulationTestCase {
+ sender := env.Accounts[0]
+ user := env.Accounts[1]
+
+ appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{
+ ApprovalProgram: returnFirstAppArgProgram,
+ ClearStateProgram: returnFirstAppArgProgram,
+ GlobalStateSchema: basics.StateSchema{
+ NumUint: 1,
+ },
+ })
+ env.OptIntoApp(user.Addr, appID)
+
+ clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: user.Addr,
+ ApplicationID: appID,
+ OnCompletion: transactions.ClearStateOC,
+ ApplicationArgs: [][]byte{}, // No app args, will cause error
+ })
+ otherAppCall := env.TxnInfo.NewTxn(txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: user.Addr,
+ ApplicationID: appID,
+ ApplicationArgs: [][]byte{{1}},
+ })
+
+ txntest.Group(&clearStateTxn, &otherAppCall)
+
+ signedClearStateTxn := clearStateTxn.Txn().Sign(user.Sk)
+ signedOtherAppCall := otherAppCall.Txn().Sign(user.Sk)
+
+ return simulationTestCase{
+ input: simulation.Request{
+ TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn, signedOtherAppCall}},
+ },
+ expected: simulation.Result{
+ Version: simulation.ResultLatestVersion,
+ LastRound: env.TxnInfo.LatestRound(),
+ TxnGroups: []simulation.TxnGroupResult{
+ {
+ Txns: []simulation.TxnResult{
+ {
+ // No EvalDelta changes because the clear state failed
+ AppBudgetConsumed: 14,
+ },
+ {
+ Txn: transactions.SignedTxnWithAD{
+ ApplyData: transactions.ApplyData{
+ EvalDelta: transactions.EvalDelta{
+ GlobalDelta: basics.StateDelta{
+ "counter": {
+ Action: basics.SetUintAction,
+ Uint: 3,
+ },
+ },
+ },
+ },
+ },
+ AppBudgetConsumed: 16,
+ },
+ },
+ AppBudgetAdded: 1400,
+ AppBudgetConsumed: 30,
+ },
+ },
+ },
+ }
+ })
+}
+
+func TestErrorAfterClearStateError(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ simulationTest(t, func(env simulationtesting.Environment) simulationTestCase {
+ sender := env.Accounts[0]
+ user := env.Accounts[1]
+
+ appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{
+ ApprovalProgram: returnFirstAppArgProgram,
+ ClearStateProgram: returnFirstAppArgProgram,
+ GlobalStateSchema: basics.StateSchema{
+ NumUint: 1,
+ },
+ })
+ env.OptIntoApp(user.Addr, appID)
+
+ clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: user.Addr,
+ ApplicationID: appID,
+ OnCompletion: transactions.ClearStateOC,
+ ApplicationArgs: [][]byte{}, // No app args, will cause error
+ })
+ otherAppCall := env.TxnInfo.NewTxn(txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: user.Addr,
+ ApplicationID: appID,
+ ApplicationArgs: [][]byte{{0}},
+ })
+
+ txntest.Group(&clearStateTxn, &otherAppCall)
+
+ signedClearStateTxn := clearStateTxn.Txn().Sign(user.Sk)
+ signedOtherAppCall := otherAppCall.Txn().Sign(user.Sk)
+
+ return simulationTestCase{
+ input: simulation.Request{
+ TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn, signedOtherAppCall}},
+ },
+ expectedError: "transaction rejected by ApprovalProgram",
+ expected: simulation.Result{
+ Version: simulation.ResultLatestVersion,
+ LastRound: env.TxnInfo.LatestRound(),
+ TxnGroups: []simulation.TxnGroupResult{
+ {
+ Txns: []simulation.TxnResult{
+ {
+ // No EvalDelta changes because the clear state failed
+ AppBudgetConsumed: 14,
+ },
+ {
+ Txn: transactions.SignedTxnWithAD{
+ ApplyData: transactions.ApplyData{
+ EvalDelta: transactions.EvalDelta{
+ GlobalDelta: basics.StateDelta{
+ "counter": {
+ Action: basics.SetUintAction,
+ Uint: 3,
+ },
+ },
+ },
+ },
+ },
+ AppBudgetConsumed: 16,
+ },
+ },
+ AppBudgetAdded: 1400,
+ AppBudgetConsumed: 30,
+ FailedAt: simulation.TxnPath{1},
+ },
+ },
+ },
+ }
+ })
+}
+
func TestAppCallOverBudget(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
@@ -3992,242 +4240,216 @@ int 1`,
})
}
-func TestGlobalStateTypeChange(t *testing.T) {
+func TestAppLocalGlobalStateChangeClearStateRollback(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
+ for _, shouldError := range []bool{false, true} {
+ shouldError := shouldError
+ t.Run(fmt.Sprintf("shouldError=%v", shouldError), func(t *testing.T) {
+ t.Parallel()
+ simulationTest(t, func(env simulationtesting.Environment) simulationTestCase {
+ sender := env.Accounts[0]
- simulationTest(t, func(env simulationtesting.Environment) simulationTestCase {
- sender := env.Accounts[0]
-
- futureAppID := basics.AppIndex(1001)
+ approvalProgram := `#pragma version 8
+int 1`
+ clearStateProgram := `#pragma version 8
+byte "global key"
+byte "global value"
+app_global_put
- createTxn := env.TxnInfo.NewTxn(txntest.Txn{
- Type: protocol.ApplicationCallTx,
- Sender: sender.Addr,
- ApplicationID: 0,
- GlobalStateSchema: basics.StateSchema{NumUint: 1, NumByteSlice: 1},
- ApprovalProgram: `#pragma version 8
-txn ApplicationID
-bz end // Do nothing during create
+txn Sender
+byte "local key"
+byte "local value"
+app_local_put
+`
-byte "global-key"
-int 0xdecaf
-app_global_put
-byte "global-key"
-byte "welt am draht"
-app_global_put
+ if shouldError {
+ clearStateProgram += "err"
+ } else {
+ clearStateProgram += "int 0"
+ }
-end:
- int 1
-`,
- ClearStateProgram: `#pragma version 8
-int 1`,
- })
+ createdAppID := env.CreateApp(sender.Addr, simulationtesting.AppParams{
+ GlobalStateSchema: basics.StateSchema{NumByteSlice: 1},
+ LocalStateSchema: basics.StateSchema{NumByteSlice: 1},
+ ApprovalProgram: approvalProgram,
+ ClearStateProgram: clearStateProgram,
+ })
- op, err := logic.AssembleString(createTxn.ApprovalProgram.(string))
- require.NoError(t, err)
- progHash := crypto.Hash(op.Program)
+ op, err := logic.AssembleString(clearStateProgram)
+ require.NoError(t, err)
+ progHash := crypto.Hash(op.Program)
- globalStateCall := env.TxnInfo.NewTxn(txntest.Txn{
- Type: protocol.ApplicationCallTx,
- Sender: sender.Addr,
- ApplicationID: futureAppID,
- })
+ env.OptIntoApp(sender.Addr, createdAppID)
- txntest.Group(&createTxn, &globalStateCall)
+ clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: sender.Addr,
+ ApplicationID: createdAppID,
+ OnCompletion: transactions.ClearStateOC,
+ })
- signedCreate := createTxn.Txn().Sign(sender.Sk)
- signedGlobalStateCall := globalStateCall.Txn().Sign(sender.Sk)
+ signedClearStateTxn := clearStateTxn.Txn().Sign(sender.Sk)
- return simulationTestCase{
- input: simulation.Request{
- TxnGroups: [][]transactions.SignedTxn{
- {signedCreate, signedGlobalStateCall},
- },
- TraceConfig: simulation.ExecTraceConfig{
- Enable: true,
- Stack: true,
- Scratch: true,
- State: true,
- },
- },
- developerAPI: true,
- expected: simulation.Result{
- Version: simulation.ResultLatestVersion,
- LastRound: env.TxnInfo.LatestRound(),
- TraceConfig: simulation.ExecTraceConfig{
- Enable: true,
- Stack: true,
- Scratch: true,
- State: true,
- },
- TxnGroups: []simulation.TxnGroupResult{
+ clearStateRollbackError := ""
+ clearStateProgramTrace := []simulation.OpcodeTraceUnit{
{
- Txns: []simulation.TxnResult{
- // App creation
+ PC: 1,
+ StackAdded: []basics.TealValue{
{
- Txn: transactions.SignedTxnWithAD{
- ApplyData: transactions.ApplyData{
- ApplicationID: futureAppID,
- },
+ Type: basics.TealBytesType,
+ Bytes: "global key",
+ },
+ },
+ },
+ {
+ PC: 13,
+ StackAdded: []basics.TealValue{
+ {
+ Type: basics.TealBytesType,
+ Bytes: "global value",
+ },
+ },
+ },
+ {
+ PC: 27,
+ StackPopCount: 2,
+ StateChanges: []simulation.StateOperation{
+ {
+ AppStateOp: logic.AppStateWrite,
+ AppState: logic.GlobalState,
+ AppID: createdAppID,
+ Key: "global key",
+ NewValue: basics.TealValue{
+ Type: basics.TealBytesType,
+ Bytes: "global value",
},
- AppBudgetConsumed: 4,
- Trace: &simulation.TransactionTrace{
- ApprovalProgramTrace: []simulation.OpcodeTraceUnit{
- {
- PC: 1,
- },
- {
- PC: 14,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealUintType,
- },
- },
- },
- {
- PC: 16,
- StackPopCount: 1,
- },
- {
- PC: 42,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealUintType,
- Uint: 1,
- },
- },
- },
- },
- ApprovalProgramHash: progHash,
+ },
+ },
+ },
+ {
+ PC: 28,
+ StackAdded: []basics.TealValue{
+ {
+ Type: basics.TealBytesType,
+ Bytes: string(sender.Addr[:]),
+ },
+ },
+ },
+ {
+ PC: 30,
+ StackAdded: []basics.TealValue{
+ {
+ Type: basics.TealBytesType,
+ Bytes: "local key",
+ },
+ },
+ },
+ {
+ PC: 41,
+ StackAdded: []basics.TealValue{
+ {
+ Type: basics.TealBytesType,
+ Bytes: "local value",
+ },
+ },
+ },
+ {
+ PC: 54,
+ StackPopCount: 3,
+ StateChanges: []simulation.StateOperation{
+ {
+ AppStateOp: logic.AppStateWrite,
+ AppState: logic.LocalState,
+ AppID: createdAppID,
+ Account: sender.Addr,
+ Key: "local key",
+ NewValue: basics.TealValue{
+ Type: basics.TealBytesType,
+ Bytes: "local value",
},
},
- // Global
+ },
+ },
+ {
+ PC: 55,
+ StackAdded: []basics.TealValue{
{
- Txn: transactions.SignedTxnWithAD{
- ApplyData: transactions.ApplyData{
- EvalDelta: transactions.EvalDelta{
- GlobalDelta: basics.StateDelta{
- "global-key": basics.ValueDelta{
- Bytes: "welt am draht",
- Action: basics.SetBytesAction,
- },
- },
+ Type: basics.TealUintType,
+ Uint: 0,
+ },
+ },
+ },
+ }
+
+ if shouldError {
+ clearStateRollbackError = "err opcode executed"
+ clearStateProgramTrace[len(clearStateProgramTrace)-1].StackAdded = nil
+ }
+
+ return simulationTestCase{
+ input: simulation.Request{
+ TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn}},
+ TraceConfig: simulation.ExecTraceConfig{
+ Enable: true,
+ Stack: true,
+ Scratch: true,
+ State: true,
+ },
+ },
+ developerAPI: true,
+ expected: simulation.Result{
+ Version: simulation.ResultLatestVersion,
+ LastRound: env.TxnInfo.LatestRound(),
+ TraceConfig: simulation.ExecTraceConfig{
+ Enable: true,
+ Stack: true,
+ Scratch: true,
+ State: true,
+ },
+ TxnGroups: []simulation.TxnGroupResult{
+ {
+ Txns: []simulation.TxnResult{
+ {
+ AppBudgetConsumed: 8,
+ Trace: &simulation.TransactionTrace{
+ ClearStateProgramTrace: clearStateProgramTrace,
+ ClearStateProgramHash: progHash,
+ ClearStateRollback: true,
+ ClearStateRollbackError: clearStateRollbackError,
},
},
},
- AppBudgetConsumed: 10,
- Trace: &simulation.TransactionTrace{
- ApprovalProgramTrace: []simulation.OpcodeTraceUnit{
- {
- PC: 1,
- },
- {
- PC: 14,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealUintType,
- Uint: uint64(futureAppID),
- },
- },
- },
- {
- PC: 16,
- StackPopCount: 1,
- },
- {
- PC: 19,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealBytesType,
- Bytes: "global-key",
- },
- },
- },
- {
- PC: 20,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealUintType,
- Uint: 0xdecaf,
- },
- },
- },
- {
- PC: 24,
- StateChanges: []simulation.StateOperation{
- {
- AppStateOp: logic.AppStateWrite,
- AppState: logic.GlobalState,
- AppID: futureAppID,
- Key: "global-key",
- NewValue: basics.TealValue{
- Type: basics.TealUintType,
- Uint: 0xdecaf,
- },
- },
- },
- StackPopCount: 2,
- },
- {
- PC: 25,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealBytesType,
- Bytes: "global-key",
- },
- },
- },
- {
- PC: 26,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealBytesType,
- Bytes: "welt am draht",
- },
- },
- },
- {
- PC: 41,
- StackPopCount: 2,
- StateChanges: []simulation.StateOperation{
- {
- AppStateOp: logic.AppStateWrite,
- AppState: logic.GlobalState,
- AppID: futureAppID,
- Key: "global-key",
- NewValue: basics.TealValue{
- Type: basics.TealBytesType,
- Bytes: "welt am draht",
- },
- },
- },
- },
- {
- PC: 42,
- StackAdded: []basics.TealValue{
- {
- Type: basics.TealUintType,
- Uint: 1,
- },
- },
- },
+ AppBudgetAdded: 700,
+ AppBudgetConsumed: 8,
+ },
+ },
+ InitialStates: &simulation.ResourcesInitialStates{
+ AllAppsInitialStates: simulation.AppsInitialStates{
+ createdAppID: {
+ AppLocals: map[basics.Address]simulation.AppKVPairs{},
+ AppGlobals: simulation.AppKVPairs{},
+ AppBoxes: simulation.AppKVPairs{},
+ // It's fine to leave the keys in "CreatedX" for two reasons:
+ // 1. These fields really just mean state was accessed that
+ // didn't exist before, so we shouldn't try to report an
+ // initial value.
+ // 2. These values are not included in the REST API, so they are
+ // not going to confuse users.
+ CreatedGlobals: util.MakeSet("global key"),
+ CreatedBoxes: make(util.Set[string]),
+ CreatedLocals: map[basics.Address]util.Set[string]{
+ sender.Addr: util.MakeSet("local key"),
},
- ApprovalProgramHash: progHash,
},
},
+ CreatedApp: util.Set[basics.AppIndex]{},
},
- AppBudgetAdded: 1400,
- AppBudgetConsumed: 14,
},
- },
- InitialStates: &simulation.ResourcesInitialStates{
- AllAppsInitialStates: make(simulation.AppsInitialStates),
- CreatedApp: util.MakeSet(futureAppID),
- },
- },
- }
- })
+ }
+ })
+ })
+ }
}
func TestGlobalStateTypeChangeFailure(t *testing.T) {
diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go
index a53d2f8ec..5c9d2ad8e 100644
--- a/ledger/simulation/simulator_test.go
+++ b/ledger/simulation/simulator_test.go
@@ -222,7 +222,7 @@ int 1`,
mocktracer.BeforeProgram(logic.ModeSig),
mocktracer.BeforeOpcode(),
mocktracer.AfterOpcode(false),
- mocktracer.AfterProgram(logic.ModeSig, false),
+ mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass),
// Txn evaluation
mocktracer.BeforeBlock(block.Block().Round()),
mocktracer.BeforeTxnGroup(2),
@@ -232,7 +232,7 @@ int 1`,
mocktracer.BeforeProgram(logic.ModeApp),
mocktracer.BeforeOpcode(),
mocktracer.AfterOpcode(false),
- mocktracer.AfterProgram(logic.ModeApp, false),
+ mocktracer.AfterProgram(logic.ModeApp, mocktracer.ProgramResultPass),
mocktracer.AfterTxn(protocol.ApplicationCallTx, evalBlock.Payset[1].ApplyData, false),
mocktracer.AfterTxnGroup(2, &expectedDelta, false),
//Block evaluation
diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go
index afc6a2124..99ac9b416 100644
--- a/ledger/simulation/trace.go
+++ b/ledger/simulation/trace.go
@@ -283,16 +283,26 @@ type TransactionTrace struct {
ApprovalProgramTrace []OpcodeTraceUnit
// ApprovalProgramHash stands for the hash digest of approval program bytecode executed during simulation
ApprovalProgramHash crypto.Digest
+
// ClearStateProgramTrace stands for a slice of OpcodeTraceUnit over application call on clear-state program
ClearStateProgramTrace []OpcodeTraceUnit
// ClearStateProgramHash stands for the hash digest of clear state program bytecode executed during simulation
ClearStateProgramHash crypto.Digest
+ // ClearStateRollback, if true, indicates that the clear state program failed and any persistent state changes
+ // it produced should be reverted once the program exits.
+ ClearStateRollback bool
+ // ClearStateRollbackError contains the error message explaining why the clear state program failed. This
+ // field will only be populated if ClearStateRollback is true and the failure was due to an execution error.
+ ClearStateRollbackError string
+
// LogicSigTrace contains the trace for a logicsig evaluation, if the transaction is approved by a logicsig.
LogicSigTrace []OpcodeTraceUnit
// LogicSigHash stands for the hash digest of logic sig bytecode executed during simulation
LogicSigHash crypto.Digest
+
// programTraceRef points to one of ApprovalProgramTrace, ClearStateProgramTrace, and LogicSigTrace during simulation.
programTraceRef *[]OpcodeTraceUnit
+
// InnerTraces contains the traces for inner transactions, if this transaction spawned any. This
// object only contains traces for inners that are immediate children of this transaction.
// Grandchild traces will be present inside the TransactionTrace of their parent.
diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go
index 2bc600ee3..e590250c9 100644
--- a/ledger/simulation/tracer.go
+++ b/ledger/simulation/tracer.go
@@ -185,12 +185,14 @@ func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore
}
}
-func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) {
+func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData, omitEvalDelta bool) {
applyDataOfCurrentTxn := tracer.mustGetApplyDataAtPath(tracer.absolutePath())
- // Copy everything except the EvalDelta, since that has been kept up-to-date after every op
evalDelta := applyDataOfCurrentTxn.EvalDelta
*applyDataOfCurrentTxn = applyData
- applyDataOfCurrentTxn.EvalDelta = evalDelta
+ if omitEvalDelta {
+ // If omitEvalDelta is true, restore the EvalDelta from applyDataOfCurrentTxn
+ applyDataOfCurrentTxn.EvalDelta = evalDelta
+ }
}
func (tracer *evalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) {
@@ -245,7 +247,7 @@ func (tracer *evalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) {
func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) {
tracer.handleError(evalError)
- tracer.saveApplyData(ad)
+ tracer.saveApplyData(ad, evalError != nil)
// if the current transaction + simulation condition would lead to exec trace making
// we should clean them up from tracer.execTraceStack.
if tracer.result.ReturnTrace() {
@@ -412,7 +414,9 @@ func (tracer *evalTracer) AfterOpcode(cx *logic.EvalContext, evalError error) {
}
if cx.RunMode() == logic.ModeApp {
- tracer.handleError(evalError)
+ if cx.TxnGroup[groupIndex].Txn.ApplicationCallTxnFields.OnCompletion != transactions.ClearStateOC {
+ tracer.handleError(evalError)
+ }
if evalError == nil && tracer.unnamedResourcePolicy != nil {
if err := tracer.unnamedResourcePolicy.tracker.reconcileBoxWriteBudget(cx.BoxDirtyBytes(), cx.Proto.BytesPerBoxReference); err != nil {
// This should never happen, since we limit the IO budget to tracer.unnamedResourcePolicy.assignment.maxPossibleBoxIOBudget
@@ -481,7 +485,7 @@ func (tracer *evalTracer) BeforeProgram(cx *logic.EvalContext) {
}
}
-func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, evalError error) {
+func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, pass bool, evalError error) {
groupIndex := cx.GroupIndex()
if cx.RunMode() == logic.ModeSig {
@@ -497,5 +501,15 @@ func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, evalError error) {
// If it is an inner app call, roll up its cost to the top level transaction.
tracer.result.TxnGroups[0].Txns[tracer.relativeCursor[0]].AppBudgetConsumed += uint64(cx.Cost())
- tracer.handleError(evalError)
+ if cx.TxnGroup[groupIndex].Txn.ApplicationCallTxnFields.OnCompletion == transactions.ClearStateOC {
+ if tracer.result.ReturnTrace() && (!pass || evalError != nil) {
+ txnTrace := tracer.execTraceStack[len(tracer.execTraceStack)-1]
+ txnTrace.ClearStateRollback = true
+ if evalError != nil {
+ txnTrace.ClearStateRollbackError = evalError.Error()
+ }
+ }
+ } else {
+ tracer.handleError(evalError)
+ }
}
diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go
index 019b50271..97693bbe2 100644
--- a/ledger/testing/consensusRange.go
+++ b/ledger/testing/consensusRange.go
@@ -60,6 +60,7 @@ var consensusByNumber = []protocol.ConsensusVersion{
protocol.ConsensusV36, // AVM v8, box storage
protocol.ConsensusV37,
protocol.ConsensusV38, // AVM v9, ECDSA pre-check, stateproofs recoverability
+ protocol.ConsensusV39, // AVM v10, logicsig opcode budget pooling, elliptic curve ops, dynamic round times
protocol.ConsensusFuture,
}
diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go
index 26e042094..f1a9c179e 100644
--- a/ledger/testing/consensusRange_test.go
+++ b/ledger/testing/consensusRange_test.go
@@ -56,6 +56,6 @@ func TestReleasedVersion(t *testing.T) {
}
require.Equal(t, versionStringFromIndex(len(consensusByNumber)-1), "vFuture")
- require.Equal(t, versionStringFromIndex(38), "v38")
+ require.Equal(t, versionStringFromIndex(39), "v39")
}
diff --git a/network/requestTracker.go b/network/requestTracker.go
index 025d75a5a..63fd4a72b 100644
--- a/network/requestTracker.go
+++ b/network/requestTracker.go
@@ -20,7 +20,10 @@ import (
"fmt"
"net"
"net/http"
+ "net/textproto"
"sort"
+ "strings"
+ "sync/atomic"
"time"
"github.com/algorand/go-deadlock"
@@ -37,15 +40,27 @@ const (
)
// TrackerRequest hold the tracking data associated with a single request.
+// It supposed by an upstream http.Handler called before the wsNetwork's ServeHTTP
+// and wsNetwork's Listener (see Accept() method)
type TrackerRequest struct {
- created time.Time
- remoteHost string
- remotePort string
- remoteAddr string
- request *http.Request
+ created time.Time
+ // remoteHost is IP address of the remote host and it is equal to either
+ // a host part of the remoteAddr or to the value of X-Forwarded-For header (UseXForwardedForAddressField config value).
+ remoteHost string
+ // remotePort is the port of the remote peer as reported by the connection or
+ // by the standard http.Request.RemoteAddr field.
+ remotePort string
+ // remoteAddr is IP:Port of the remote host retrieved from the connection
+ // or from the standard http.Request.RemoteAddr field.
+ // This field is the real address of the remote incoming connection.
+ remoteAddr string
+ // otherPublicAddr is the public address of the other node, as reported by the other node
+ // via the X-Algorand-Location header.
+ // It is used for logging and as a rootURL for when creating a new wsPeer from a request.
+ otherPublicAddr string
+
otherTelemetryGUID string
otherInstanceName string
- otherPublicAddr string
connection net.Conn
noPrune bool
}
@@ -65,6 +80,43 @@ func makeTrackerRequest(remoteAddr, remoteHost, remotePort string, createTime ti
}
}
+// remoteAddress a best guessed remote address for the request.
+// Rational is the following:
+// remoteAddress() is used either for logging or as rootURL for creating a new wsPeer.
+// rootURL is an address to connect to. It is well defined only for peers from a phonebooks,
+// and for incoming peers the best guess is either otherPublicAddr, remoteHost, or remoteAddr.
+// - otherPublicAddr is provided by a remote peer by X-Algorand-Location header and cannot be trusted,
+// but can be used if remoteHost matches to otherPublicAddr value. In this case otherPublicAddr is a better guess
+// for a rootURL because it might include a port.
+// - remoteHost is either a real address of the remote peer or a value of X-Forwarded-For header.
+// Use it if remoteHost was taken from X-Forwarded-For header.
+// Note, the remoteHost does not include a port since a listening port is not known.
+// - remoteAddr is used otherwise.
+func (tr *TrackerRequest) remoteAddress() string {
+ if len(tr.otherPublicAddr) != 0 {
+ url, err := ParseHostOrURL(tr.otherPublicAddr)
+ if err == nil && len(tr.remoteHost) > 0 && url.Hostname() == tr.remoteHost {
+ return tr.otherPublicAddr
+ }
+ }
+ url, err := ParseHostOrURL(tr.remoteAddr)
+ if err != nil {
+ // tr.remoteAddr can't be parsed so try to use tr.remoteHost
+ // there is a chance it came from a proxy and has a meaningful value
+ if len(tr.remoteHost) != 0 {
+ return tr.remoteHost
+ }
+ // otherwise fallback to tr.remoteAddr
+ return tr.remoteAddr
+ }
+ if url.Hostname() != tr.remoteHost {
+ // if remoteAddr's host not equal to remoteHost then the remoteHost
+ // is definitely came from a proxy, use it
+ return tr.remoteHost
+ }
+ return tr.remoteAddr
+}
+
// hostIncomingRequests holds all the requests that are originating from a single host.
type hostIncomingRequests struct {
remoteHost string
@@ -139,7 +191,6 @@ func (ard *hostIncomingRequests) add(trackerRequest *TrackerRequest) {
}
// it's going to be added somewhere in the middle.
ard.requests = append(ard.requests[:itemIdx], append([]*TrackerRequest{trackerRequest}, ard.requests[itemIdx:]...)...)
- return
}
// countConnections counts the number of connection that we have that occurred after the provided specified time
@@ -222,7 +273,7 @@ type RequestTracker struct {
log logging.Logger
config config.Local
// once we detect that we have a misconfigured UseForwardedForAddress, we set this and write an warning message.
- misconfiguredUseForwardedForAddress bool
+ misconfiguredUseForwardedForAddress atomic.Bool
listener net.Listener // this is the downsteam listener
@@ -369,7 +420,7 @@ func (rt *RequestTracker) sendBlockedConnectionResponse(conn net.Conn, requestTi
func (rt *RequestTracker) pruneAcceptedConnections(pruneStartDate time.Time) {
localAddrToRemove := []net.Addr{}
for localAddr, request := range rt.acceptedConnections {
- if request.noPrune == false && request.created.Before(pruneStartDate) {
+ if !request.noPrune && request.created.Before(pruneStartDate) {
localAddrToRemove = append(localAddrToRemove, localAddr)
}
}
@@ -394,7 +445,7 @@ func (rt *RequestTracker) getWaitUntilNoConnectionsChannel(checkInterval time.Du
return len(rt.httpConnections) == 0
}
- for true {
+ for {
if checkEmpty(rt) {
close(done)
return
@@ -446,7 +497,7 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http.
trackedRequest := rt.acceptedConnections[localAddr]
if trackedRequest != nil {
// update the original tracker request so that it won't get pruned.
- if trackedRequest.noPrune == false {
+ if !trackedRequest.noPrune {
trackedRequest.noPrune = true
rt.hostRequests.convertToAdditionalRequest(trackedRequest)
}
@@ -461,10 +512,9 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http.
}
// update the origin address.
- rt.updateRequestRemoteAddr(trackedRequest, request)
+ rt.remoteHostProxyFix(request.Header, trackedRequest)
rt.httpConnectionsMu.Lock()
- trackedRequest.request = request
trackedRequest.otherTelemetryGUID, trackedRequest.otherInstanceName, trackedRequest.otherPublicAddr = getCommonHeaders(request.Header)
rt.httpHostRequests.addRequest(trackedRequest)
rt.httpHostRequests.pruneRequests(rateLimitingWindowStartTime)
@@ -503,13 +553,12 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http.
}
-// updateRequestRemoteAddr updates the origin IP address in both the trackedRequest as well as in the request.RemoteAddr string
-func (rt *RequestTracker) updateRequestRemoteAddr(trackedRequest *TrackerRequest, request *http.Request) {
- originIP := rt.getForwardedConnectionAddress(request.Header)
+// remoteHostProxyFix updates the origin IP address in the trackedRequest
+func (rt *RequestTracker) remoteHostProxyFix(header http.Header, trackedRequest *TrackerRequest) {
+ originIP := rt.getForwardedConnectionAddress(header)
if originIP == nil {
return
}
- request.RemoteAddr = originIP.String() + ":" + trackedRequest.remotePort
trackedRequest.remoteHost = originIP.String()
}
@@ -518,13 +567,29 @@ func (rt *RequestTracker) getForwardedConnectionAddress(header http.Header) (ip
if rt.config.UseXForwardedForAddressField == "" {
return
}
- forwardedForString := header.Get(rt.config.UseXForwardedForAddressField)
+ var forwardedForString string
+ // if we're using the standard X-Forwarded-For header(s), we need to parse it.
+ // as UseXForwardedForAddressField defines, use the last value from the last X-Forwarded-For header's list of values.
+ if textproto.CanonicalMIMEHeaderKey(rt.config.UseXForwardedForAddressField) == "X-Forwarded-For" {
+ forwardedForStrings := header.Values(rt.config.UseXForwardedForAddressField)
+ if len(forwardedForStrings) != 0 {
+ forwardedForString = forwardedForStrings[len(forwardedForStrings)-1]
+ ips := strings.Split(forwardedForString, ",")
+ if len(ips) != 0 {
+ forwardedForString = strings.TrimSpace(ips[len(ips)-1])
+ } else {
+ // looks like not possble case now but it's better to handle
+ rt.log.Warnf("header X-Forwarded-For has an invalid value: '%s'", forwardedForString)
+ forwardedForString = ""
+ }
+ }
+ } else {
+ forwardedForString = header.Get(rt.config.UseXForwardedForAddressField)
+ }
+
if forwardedForString == "" {
- rt.httpConnectionsMu.Lock()
- defer rt.httpConnectionsMu.Unlock()
- if !rt.misconfiguredUseForwardedForAddress {
+ if rt.misconfiguredUseForwardedForAddress.CompareAndSwap(false, true) {
rt.log.Warnf("UseForwardedForAddressField is configured as '%s', but no value was retrieved from header", rt.config.UseXForwardedForAddressField)
- rt.misconfiguredUseForwardedForAddress = true
}
return
}
diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go
index 8998b41f7..65349987e 100644
--- a/network/requestTracker_test.go
+++ b/network/requestTracker_test.go
@@ -17,7 +17,9 @@
package network
import (
+ "bytes"
"math/rand"
+ "net/http"
"testing"
"time"
@@ -170,8 +172,35 @@ func TestRateLimiting(t *testing.T) {
}
}
+func TestRemoteAddress(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ tr := makeTrackerRequest("127.0.0.1:444", "", "", time.Now(), nil)
+ require.Equal(t, "127.0.0.1:444", tr.remoteAddr)
+ require.Equal(t, "127.0.0.1", tr.remoteHost)
+ require.Equal(t, "444", tr.remotePort)
+
+ require.Equal(t, "127.0.0.1:444", tr.remoteAddress())
+
+ // remoteHost set to something else via X-Forwared-For HTTP headers
+ tr.remoteHost = "10.0.0.1"
+ require.Equal(t, "10.0.0.1", tr.remoteAddress())
+
+ // otherPublicAddr is set via X-Algorand-Location HTTP header
+ // and matches to the remoteHost
+ tr.otherPublicAddr = "10.0.0.1:555"
+ require.Equal(t, "10.0.0.1:555", tr.remoteAddress())
+
+ // otherPublicAddr does not match remoteHost
+ tr.remoteHost = "127.0.0.1"
+ tr.otherPublicAddr = "127.0.0.99:555"
+ require.Equal(t, "127.0.0.1:444", tr.remoteAddress())
+}
+
func TestIsLocalHost(t *testing.T) {
partitiontest.PartitionTest(t)
+ t.Parallel()
require.True(t, isLocalhost("localhost"))
require.True(t, isLocalhost("127.0.0.1"))
@@ -183,3 +212,101 @@ func TestIsLocalHost(t *testing.T) {
require.False(t, isLocalhost("0.0.0.0"))
require.False(t, isLocalhost("127.0.0.0"))
}
+
+func TestGetForwardedConnectionAddress(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ var bufNewLogger bytes.Buffer
+ log := logging.NewLogger()
+ log.SetOutput(&bufNewLogger)
+
+ rt := RequestTracker{log: log}
+ header := http.Header{}
+
+ ip := rt.getForwardedConnectionAddress(header)
+ require.Nil(t, ip)
+ msgs := bufNewLogger.String()
+ require.Empty(t, msgs)
+
+ rt.config.UseXForwardedForAddressField = "X-Custom-Addr"
+ ip = rt.getForwardedConnectionAddress(header)
+ require.Nil(t, ip)
+ msgs = bufNewLogger.String()
+ require.NotEmpty(t, msgs)
+ require.Contains(t, msgs, "UseForwardedForAddressField is configured as 'X-Custom-Addr'")
+
+ // try again and ensure the message is not logged second time.
+ bufNewLogger.Reset()
+ ip = rt.getForwardedConnectionAddress(header)
+ require.Nil(t, ip)
+ msgs = bufNewLogger.String()
+ require.Empty(t, msgs)
+
+ // check a custom address can be parsed successfully.
+ header.Set("X-Custom-Addr", "123.123.123.123")
+ ip = rt.getForwardedConnectionAddress(header)
+ require.NotNil(t, ip)
+ require.Equal(t, "123.123.123.123", ip.String())
+ msgs = bufNewLogger.String()
+ require.Empty(t, msgs)
+
+ // check a custom address in a form of a list can not be parsed,
+ // this is the original behavior since the Release.
+ header.Set("X-Custom-Addr", "123.123.123.123, 234.234.234.234")
+ ip = rt.getForwardedConnectionAddress(header)
+ require.Nil(t, ip)
+ msgs = bufNewLogger.String()
+ require.NotEmpty(t, msgs)
+ require.Contains(t, msgs, "unable to parse origin address")
+
+ // "X-Forwarded-For
+ bufNewLogger.Reset()
+ rt.misconfiguredUseForwardedForAddress.Store(false)
+ rt.config.UseXForwardedForAddressField = "X-Forwarded-For"
+ header = http.Header{}
+
+ // check "X-Forwarded-For" empty value.
+ ip = rt.getForwardedConnectionAddress(header)
+ require.Nil(t, ip)
+ msgs = bufNewLogger.String()
+ require.NotEmpty(t, msgs)
+ require.Contains(t, msgs, "UseForwardedForAddressField is configured as 'X-Forwarded-For'")
+ bufNewLogger.Reset()
+
+ // check "X-Forwarded-For" single value.
+ header.Set("X-Forwarded-For", "123.123.123.123")
+ ip = rt.getForwardedConnectionAddress(header)
+ require.NotNil(t, ip)
+ require.Equal(t, "123.123.123.123", ip.String())
+ msgs = bufNewLogger.String()
+ require.Empty(t, msgs)
+
+ // check "X-Forwarded-For" list values - the last one is used,
+ // this is a new behavior.
+ bufNewLogger.Reset()
+ rt.config.UseXForwardedForAddressField = "X-Forwarded-For"
+ header.Set("X-Forwarded-For", "123.123.123.123, 234.234.234.234")
+ ip = rt.getForwardedConnectionAddress(header)
+ require.NotNil(t, ip)
+ require.Equal(t, "234.234.234.234", ip.String())
+ msgs = bufNewLogger.String()
+ require.Empty(t, msgs)
+
+ // check multile X-Forwarded-For headers - the last one should be used
+ header.Set("X-Forwarded-For", "127.0.0.1")
+ header.Add("X-Forwarded-For", "234.234.234.234")
+ ip = rt.getForwardedConnectionAddress(header)
+ require.NotNil(t, ip)
+ require.Equal(t, "234.234.234.234", ip.String())
+ msgs = bufNewLogger.String()
+ require.Empty(t, msgs)
+
+ header.Set("X-Forwarded-For", "127.0.0.1")
+ header.Add("X-Forwarded-For", "123.123.123.123, 234.234.234.234")
+ ip = rt.getForwardedConnectionAddress(header)
+ require.NotNil(t, ip)
+ require.Equal(t, "234.234.234.234", ip.String())
+ msgs = bufNewLogger.String()
+ require.Empty(t, msgs)
+}
diff --git a/network/websocketProxy_test.go b/network/websocketProxy_test.go
new file mode 100644
index 000000000..e0888c544
--- /dev/null
+++ b/network/websocketProxy_test.go
@@ -0,0 +1,322 @@
+// Copyright (C) 2019-2023 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/>.
+
+// This is a simple reverse proxy for websocket connections. It is used to to test
+// ws network behavior when UseXForwardedForAddressField is enabled.
+// Not suitable for production use.
+package network
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/algorand/websocket"
+ "github.com/stretchr/testify/require"
+)
+
+var testProxyUpgrader = websocket.Upgrader{
+ ReadBufferSize: 4096,
+ WriteBufferSize: 4096,
+ EnableCompression: false,
+}
+
+var testProxyDialer = net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+}
+
+var testProxyWebsocketDialer = websocket.Dialer{
+ HandshakeTimeout: 45 * time.Second,
+ EnableCompression: false,
+ NetDialContext: testProxyDialer.DialContext,
+ NetDial: testProxyDialer.Dial,
+ MaxHeaderSize: wsMaxHeaderBytes,
+}
+
+type websocketProxy struct {
+ upstream string
+ overrideXForwardedFor string
+}
+
+// ServeHTTP implements http.Handler
+func (w *websocketProxy) ServeHTTP(response http.ResponseWriter, request *http.Request) {
+ // copy all but upgrade headers otherwise Dial complains about duplicate headers
+ headers := http.Header{}
+ for k, v := range request.Header {
+ // filter out upgrade headers since Upgrader will add them
+ if k == "Sec-Websocket-Key" || k == "Sec-Websocket-Version" || k == "Connection" || k == "Upgrade" {
+ continue
+ }
+ headers[k] = v
+ }
+
+ // set X-Forwarded-For
+ url, err := ParseHostOrURL(request.RemoteAddr)
+ if err != nil {
+ http.Error(response, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if w.overrideXForwardedFor != "" {
+ headers.Set("X-Forwarded-For", w.overrideXForwardedFor)
+ } else {
+ headers.Set("X-Forwarded-For", url.Hostname())
+ }
+
+ upURL := *request.URL
+ upURL.Host = w.upstream
+ upURL.Scheme = "ws"
+
+ // dial upstream
+ upstreamConn, upResp, err := testProxyWebsocketDialer.Dial(upURL.String(), headers)
+ if err != nil {
+ msg := fmt.Sprintf("websocketProxy: error dialing upstream %s: %s", upURL.String(), err.Error())
+ if upResp != nil {
+ msg = fmt.Sprintf("%s: %v", msg, *upResp)
+ }
+ http.Error(response, msg, http.StatusInternalServerError)
+ return
+ }
+ defer upstreamConn.Close()
+
+ // upgeade the client
+ remoteConn, err := testProxyUpgrader.Upgrade(response, request, upResp.Header)
+ if err != nil {
+ http.Error(response, "websocketProxy: error upgrading connection: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ defer remoteConn.Close()
+
+ remoteConn.SetReadLimit(MaxMessageLength)
+ upstreamConn.SetReadLimit(MaxMessageLength)
+
+ errCh := make(chan error, 1)
+ go w.forward(remoteConn, upstreamConn, errCh)
+ go w.forward(upstreamConn, remoteConn, errCh)
+
+ err = <-errCh
+ if e, ok := err.(*websocket.CloseError); !ok {
+ // calling http.Error causes "response.WriteHeader on hijacked connection" error
+ fmt.Printf("websocketProxy: closing error forwarding connection: %s\n", err.Error())
+ } else if e.Code != websocket.CloseNormalClosure {
+ fmt.Printf("websocketProxy: closing error forwarding connection: %s\n", err.Error())
+ }
+}
+
+func (w *websocketProxy) forward(dst, src *websocket.Conn, errCh chan error) {
+ for {
+ msgType, msg, err := src.ReadMessage()
+ if err != nil {
+ errCh <- err
+ return
+ }
+ err = dst.WriteMessage(msgType, msg)
+ if err != nil {
+ errCh <- err
+ return
+ }
+ }
+}
+
+// TestWebsocketProxy checks the websocket proxy implementation:
+// it forwards messages ands adds X-Forwarded-For header
+func TestWebsocketProxy(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ var headerChecker func(headers http.Header) // define below when all addresses are known
+
+ // setup the upstream server
+ upstreamAddr := "127.0.0.1:"
+ upstreamMux := http.NewServeMux()
+ upstreamMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {})
+ upstreamMux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
+ // handler returns the same message it receives with a prefix 'pong:'
+ t.Logf("upsream received connection from %s\n", r.RemoteAddr)
+ headerChecker(r.Header)
+
+ conn, err := testProxyUpgrader.Upgrade(w, r, nil)
+ require.NoError(t, err)
+ conn.SetReadLimit(2 * 1024)
+ messageType, p, err := conn.ReadMessage()
+ require.NoError(t, err)
+ msg := append([]byte("pong:"), p...)
+ conn.WriteMessage(messageType, msg)
+ require.NoError(t, err)
+ })
+ upstreamListener, err := net.Listen("tcp", upstreamAddr)
+ require.NoError(t, err)
+ upstreamAddr = upstreamListener.Addr().String()
+ upstreamSrv := &http.Server{Addr: upstreamAddr, Handler: upstreamMux}
+ go upstreamSrv.Serve(upstreamListener)
+
+ // wait upstream to be ready
+ require.Eventually(t, func() bool {
+ resp, err := http.Get("http://" + upstreamAddr + "/status")
+ if err != nil {
+ return false
+ }
+ return resp.StatusCode == http.StatusOK
+ }, 5*time.Second, 100*time.Millisecond)
+
+ // setup the proxy
+ wsProxy := &websocketProxy{upstreamAddr, ""}
+ wsProxyMux := http.NewServeMux()
+ wsProxyMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {})
+ wsProxyMux.Handle("/ws", wsProxy)
+ wsProxyListener, err := net.Listen("tcp", "[::1]:")
+ require.NoError(t, err)
+
+ wsProxyAddr := wsProxyListener.Addr().String()
+ wsProxySrv := &http.Server{Addr: wsProxyAddr, Handler: wsProxyMux}
+ go wsProxySrv.Serve(wsProxyListener)
+
+ checked := false
+ headerChecker = func(headers http.Header) {
+ hostname, _, err := net.SplitHostPort(wsProxyAddr)
+ require.NoError(t, err)
+ require.Contains(t, headers, ("X-Forwarded-For"))
+ require.Equal(t, hostname, headers.Get("X-Forwarded-For"))
+ checked = true
+ }
+
+ // wait ws proxy to be ready
+ require.Eventually(t, func() bool {
+ resp, err := http.Get("http://" + wsProxyAddr + "/status")
+ if err != nil {
+ return false
+ }
+ return resp.StatusCode == http.StatusOK
+ }, 5*time.Second, 100*time.Millisecond)
+
+ t.Logf("upstream addr: %s", upstreamAddr)
+ t.Logf("ws proxy addr: %s", wsProxyAddr)
+
+ // now send data through the proxy
+ conn, resp, err := testProxyWebsocketDialer.Dial("ws://"+wsProxyAddr+"/ws", nil)
+ var errMsg string
+ if err != nil && resp != nil {
+ b, err0 := io.ReadAll(resp.Body)
+ require.NoError(t, err0)
+ errMsg = fmt.Sprintf("error dialing proxy: %v, body: %s", resp, b)
+ }
+ require.NoError(t, err, errMsg)
+ t.Logf("connected to %s", conn.RemoteAddr().String())
+
+ conn.SetReadLimit(2 * 1024)
+ msg := "ping"
+ conn.WriteMessage(websocket.TextMessage, []byte(msg))
+ require.NoError(t, err)
+ messageType, p, err := conn.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, websocket.TextMessage, messageType)
+ require.Equal(t, "pong:"+msg, string(p))
+
+ conn.Close()
+ err = upstreamSrv.Shutdown(context.Background())
+ require.NoError(t, err)
+ err = wsProxySrv.Shutdown(context.Background())
+ require.NoError(t, err)
+
+ // ensure the header was checked
+ require.True(t, checked)
+}
+
+func TestWebsocketProxyWsNet(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ // upstream node
+ netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"})
+ netA.requestsTracker.config.UseXForwardedForAddressField = "X-Forwarded-For"
+ netA.Start()
+ defer netA.Stop()
+ addrA, ok := netA.Address()
+ require.True(t, ok)
+ gossipA, err := netA.addrToGossipAddr(addrA)
+ require.NoError(t, err)
+
+ parsedA, err := ParseHostOrURL(gossipA)
+ require.NoError(t, err)
+
+ // setup the proxy
+ // use a fake address since all nodes are on the same machine/localhost
+ fakeXForwardedFor := "169.254.1.1"
+ wsProxy := &websocketProxy{parsedA.Host, fakeXForwardedFor}
+ wsProxyMux := http.NewServeMux()
+ wsProxyMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {})
+ wsProxyMux.Handle(parsedA.Path, wsProxy)
+ wsProxyListener, err := net.Listen("tcp", "[::1]:")
+ require.NoError(t, err)
+
+ wsProxyAddr := wsProxyListener.Addr().String()
+ wsProxySrv := &http.Server{Addr: wsProxyAddr, Handler: wsProxyMux}
+ go wsProxySrv.Serve(wsProxyListener)
+ defer wsProxySrv.Shutdown(context.Background())
+
+ // wait ws proxy to be ready
+ require.Eventually(t, func() bool {
+ resp, err := http.Get("http://" + wsProxyAddr + "/status")
+ if err != nil {
+ return false
+ }
+ return resp.StatusCode == http.StatusOK
+ }, 5*time.Second, 100*time.Millisecond)
+
+ netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"})
+ netB.Start()
+ defer netB.Stop()
+ addrB, ok := netB.Address()
+ require.True(t, ok)
+
+ t.Logf("upstream addr: %s", addrA)
+ t.Logf("ws proxy addr: %s", wsProxyAddr)
+ t.Logf("client netB addr: %s", addrB)
+
+ require.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
+ require.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut)))
+ require.Equal(t, 0, len(netB.GetPeers(PeersConnectedIn)))
+ require.Equal(t, 0, len(netB.GetPeers(PeersConnectedOut)))
+
+ wsProxyGossip, ok := netB.tryConnectReserveAddr(wsProxyAddr)
+ require.True(t, ok)
+
+ netB.wg.Add(1)
+ netB.tryConnect(wsProxyAddr, wsProxyGossip)
+
+ require.Eventually(t, func() bool {
+ return len(netB.GetPeers(PeersConnectedOut)) == 1
+ }, 5*time.Second, 10*time.Millisecond)
+
+ require.Equal(t, 1, len(netA.GetPeers(PeersConnectedIn)))
+ require.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut)))
+ require.Equal(t, 0, len(netB.GetPeers(PeersConnectedIn)))
+ require.Equal(t, 1, len(netB.GetPeers(PeersConnectedOut)))
+
+ // get peerB from the upstream node (netA)
+ // and ensure it has the expected origin/routing address as set by the proxy
+ peerB := netA.peers[0]
+ require.NotEmpty(t, peerB.originAddress)
+ require.Equal(t, fakeXForwardedFor, peerB.originAddress)
+ require.NotEqual(t, peerB.RoutingAddr(), peerB.IPAddr())
+ fakeXForwardedForParsed := net.ParseIP(fakeXForwardedFor)
+ require.NotEqual(t, fakeXForwardedForParsed, peerB.RoutingAddr())
+}
diff --git a/network/wsNetwork.go b/network/wsNetwork.go
index 7f8b3046c..d316fcd81 100644
--- a/network/wsNetwork.go
+++ b/network/wsNetwork.go
@@ -377,9 +377,9 @@ func (wn *WebsocketNetwork) PublicAddress() string {
// If except is not nil then we will not send it to that neighboring Peer.
// if wait is true then the call blocks until the packet has actually been sent to all neighbors.
func (wn *WebsocketNetwork) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error {
- dataArray := make([][]byte, 1, 1)
+ dataArray := make([][]byte, 1)
dataArray[0] = data
- tagArray := make([]protocol.Tag, 1, 1)
+ tagArray := make([]protocol.Tag, 1)
tagArray[0] = tag
return wn.broadcaster.BroadcastArray(ctx, tagArray, dataArray, wait, except)
}
@@ -947,7 +947,7 @@ func (wn *WebsocketNetwork) checkProtocolVersionMatch(otherHeaders http.Header)
// checkIncomingConnectionVariables checks the variables that were provided on the request, and compares them to the
// local server supported parameters. If all good, it returns http.StatusOK; otherwise, it write the error to the ResponseWriter
// and returns the http status.
-func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.ResponseWriter, request *http.Request) int {
+func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.ResponseWriter, request *http.Request, remoteAddrForLogging string) int {
// check to see that the genesisID in the request URI is valid and matches the supported one.
pathVars := mux.Vars(request)
otherGenesisID, hasGenesisID := pathVars["genesisID"]
@@ -958,7 +958,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo
}
if wn.GenesisID != otherGenesisID {
- wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", request.RemoteAddr, wn.GenesisID, otherGenesisID, request.Header)))
+ wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", remoteAddrForLogging, wn.GenesisID, otherGenesisID, request.Header)))
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "mismatching genesis-id"})
response.WriteHeader(http.StatusPreconditionFailed)
n, err := response.Write([]byte("mismatching genesis ID"))
@@ -973,7 +973,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo
// This is pretty harmless and some configurations of phonebooks or DNS records make this likely. Quietly filter it out.
var message string
// missing header.
- wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", request.RemoteAddr, wn.RandomID, request.Header)))
+ wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", remoteAddrForLogging, wn.RandomID, request.Header)))
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "missing random ID header"})
message = fmt.Sprintf("Request was missing a %s header", NodeRandomHeader)
response.WriteHeader(http.StatusPreconditionFailed)
@@ -985,7 +985,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo
} else if otherRandom == wn.RandomID {
// This is pretty harmless and some configurations of phonebooks or DNS records make this likely. Quietly filter it out.
var message string
- wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", request.RemoteAddr, wn.RandomID)
+ wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", remoteAddrForLogging, wn.RandomID)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "matching random ID header"})
message = fmt.Sprintf("Request included matching %s=%s header", NodeRandomHeader, otherRandom)
response.WriteHeader(http.StatusLoopDetected)
@@ -1025,7 +1025,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
matchingVersion, otherVersion := wn.checkProtocolVersionMatch(request.Header)
if matchingVersion == "" {
- wn.log.Info(filterASCII(fmt.Sprintf("new peer %s version mismatch, mine=%v theirs=%s, headers %#v", request.RemoteAddr, wn.supportedProtocolVersions, otherVersion, request.Header)))
+ wn.log.Info(filterASCII(fmt.Sprintf("new peer %s version mismatch, mine=%v theirs=%s, headers %#v", trackedRequest.remoteHost, wn.supportedProtocolVersions, otherVersion, request.Header)))
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "mismatching protocol version"})
response.WriteHeader(http.StatusPreconditionFailed)
message := fmt.Sprintf("Requested version %s not in %v mismatches server version", filterASCII(otherVersion), wn.supportedProtocolVersions)
@@ -1036,14 +1036,11 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
return
}
- if wn.checkIncomingConnectionVariables(response, request) != http.StatusOK {
+ if wn.checkIncomingConnectionVariables(response, request, trackedRequest.remoteAddress()) != http.StatusOK {
// we've already logged and written all response(s).
return
}
- // if UseXForwardedForAddressField is not empty, attempt to override the otherPublicAddr with the X Forwarded For origin
- trackedRequest.otherPublicAddr = trackedRequest.remoteAddr
-
responseHeader := make(http.Header)
wn.setHeaders(responseHeader)
responseHeader.Set(ProtocolVersionHeader, matchingVersion)
@@ -1063,7 +1060,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
peerIDChallenge, peerID, err = wn.identityScheme.VerifyRequestAndAttachResponse(responseHeader, request.Header)
if err != nil {
networkPeerIdentityError.Inc(nil)
- wn.log.With("err", err).With("remote", trackedRequest.otherPublicAddr).With("local", localAddr).Warnf("peer (%s) supplied an invalid identity challenge, abandoning peering", trackedRequest.otherPublicAddr)
+ wn.log.With("err", err).With("remote", trackedRequest.remoteAddress()).With("local", localAddr).Warnf("peer (%s) supplied an invalid identity challenge, abandoning peering", trackedRequest.remoteAddr)
return
}
}
@@ -1081,7 +1078,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
}
peer := &wsPeer{
- wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.otherPublicAddr, wn.GetRoundTripper(), trackedRequest.remoteHost),
+ wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.remoteAddress(), wn.GetRoundTripper(), trackedRequest.remoteHost),
conn: wsPeerWebsocketConnImpl{conn},
outgoing: false,
InstanceName: trackedRequest.otherInstanceName,
@@ -1097,7 +1094,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
peer.TelemetryGUID = trackedRequest.otherTelemetryGUID
peer.init(wn.config, wn.outgoingMessagesBufferSize)
wn.addPeer(peer)
- wn.log.With("event", "ConnectedIn").With("remote", trackedRequest.otherPublicAddr).With("local", localAddr).Infof("Accepted incoming connection from peer %s", trackedRequest.otherPublicAddr)
+ wn.log.With("event", "ConnectedIn").With("remote", trackedRequest.remoteAddress()).With("local", localAddr).Infof("Accepted incoming connection from peer %s", trackedRequest.remoteAddr)
wn.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerEvent,
telemetryspec.PeerEventDetails{
Address: trackedRequest.remoteHost,
@@ -2047,6 +2044,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) {
}
}()
defer wn.wg.Done()
+
requestHeader := make(http.Header)
wn.setHeaders(requestHeader)
for _, supportedProtocolVersion := range wn.supportedProtocolVersions {
diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go
index 05e484843..f8eeeb71f 100644
--- a/network/wsNetwork_test.go
+++ b/network/wsNetwork_test.go
@@ -1332,8 +1332,6 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
addrA, ok := netA.Address()
require.True(t, ok)
- gossipA, err := netA.addrToGossipAddr(addrA)
- require.NoError(t, err)
addrB, ok := netB.Address()
require.True(t, ok)
@@ -1349,7 +1347,9 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
netA.wg.Add(1)
netA.tryConnect(addrB, gossipB)
// let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return len(netA.GetPeers(PeersConnectedOut)) == 1
+ }, time.Second, 50*time.Millisecond)
}
// just one A->B connection
assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
@@ -1362,17 +1362,16 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
assert.Equal(t, 1, netA.identityTracker.(*mockIdentityTracker).getInsertCount())
// netB has to wait for a final verification message over WS Handler, so pause a moment
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return netB.identityTracker.(*mockIdentityTracker).getSetCount() == 1
+ }, time.Second, 50*time.Millisecond)
+
assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getSetCount())
assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getInsertCount())
// bi-directional connection from B should not proceed
- if _, ok := netB.tryConnectReserveAddr(addrA); ok {
- netB.wg.Add(1)
- netB.tryConnect(addrA, gossipA)
- // let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
- }
+ _, ok = netB.tryConnectReserveAddr(addrA)
+ assert.False(t, ok)
// still just one A->B connection
assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
@@ -1381,9 +1380,9 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
assert.Equal(t, 0, len(netB.GetPeers(PeersConnectedOut)))
// netA never attempts to set identity as it never sees a verified identity
assert.Equal(t, 1, netA.identityTracker.(*mockIdentityTracker).getSetCount())
- // netB would attempt to add the identity to the tracker
- // but it would not end up being added
- assert.Equal(t, 2, netB.identityTracker.(*mockIdentityTracker).getSetCount())
+ // no connecton => netB does attepmt to add the identity to the tracker
+ // and it would not end up being added
+ assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getSetCount())
assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getInsertCount())
// Check deduplication again, this time from A
@@ -1391,15 +1390,19 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
// will prevent this connection from attempting in the first place
// in the real world, that isConnectedTo doesn't always trigger, if the hosts are behind
// a load balancer or other NAT
- if _, ok := netA.tryConnectReserveAddr(addrB); ok || true {
- netA.wg.Add(1)
- netA.tryConnect(addrB, gossipB)
- // let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
- }
+ _, ok = netA.tryConnectReserveAddr(addrB)
+ assert.False(t, ok)
+ netA.wg.Add(1)
+ old := networkPeerIdentityDisconnect.GetUint64Value()
+ netA.tryConnect(addrB, gossipB)
+ // let the tryConnect go forward
+ assert.Eventually(t, func() bool {
+ new := networkPeerIdentityDisconnect.GetUint64Value()
+ return new > old
+ }, time.Second, 50*time.Millisecond)
// netB never tries to add a new identity, since the connection gets abandoned before it is verified
- assert.Equal(t, 2, netB.identityTracker.(*mockIdentityTracker).getSetCount())
+ assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getSetCount())
assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getInsertCount())
// still just one A->B connection
assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
@@ -1411,11 +1414,9 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
// the underlying connection is being closed. In this case, the read loop
// on the peer will detect and close the peer. Since this is asynchronous,
// we wait and check regularly to allow the connection to settle
- assert.Eventually(
- t,
- func() bool { return len(netB.GetPeers(PeersConnectedIn)) == 1 },
- 5*time.Second,
- 100*time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return len(netB.GetPeers(PeersConnectedIn)) == 1
+ }, time.Second, 50*time.Millisecond)
// Now have A connect to node C, which has the same PublicAddress as B (e.g., because it shares the
// same public load balancer endpoint). C will have a different identity keypair and so will not be
@@ -1432,13 +1433,15 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
require.True(t, ok)
gossipC, err := netC.addrToGossipAddr(addrC)
require.NoError(t, err)
- addrC = hostAndPort(addrC)
+ assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut)))
// A connects to C (but uses addrB here to simulate case where B & C have the same PublicAddress)
netA.wg.Add(1)
netA.tryConnect(addrB, gossipC)
// let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return len(netA.GetPeers(PeersConnectedOut)) == 2
+ }, time.Second, 50*time.Millisecond)
// A->B and A->C both open
assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
@@ -1453,7 +1456,10 @@ func TestPeeringWithIdentityChallenge(t *testing.T) {
assert.Equal(t, 2, netA.identityTracker.(*mockIdentityTracker).getInsertCount())
// netC has to wait for a final verification message over WS Handler, so pause a moment
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return netC.identityTracker.(*mockIdentityTracker).getSetCount() == 1
+ }, time.Second, 50*time.Millisecond)
+
assert.Equal(t, 1, netC.identityTracker.(*mockIdentityTracker).getSetCount())
assert.Equal(t, 1, netC.identityTracker.(*mockIdentityTracker).getInsertCount())
@@ -1481,8 +1487,6 @@ func TestPeeringSenderIdentityChallengeOnly(t *testing.T) {
addrA, ok := netA.Address()
require.True(t, ok)
- gossipA, err := netA.addrToGossipAddr(addrA)
- require.NoError(t, err)
addrB, ok := netB.Address()
require.True(t, ok)
@@ -1493,12 +1497,16 @@ func TestPeeringSenderIdentityChallengeOnly(t *testing.T) {
addrA = hostAndPort(addrA)
addrB = hostAndPort(addrB)
+ assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut)))
+ assert.Equal(t, 0, len(netB.GetPeers(PeersConnectedIn)))
+
// first connection should work just fine
if _, ok := netA.tryConnectReserveAddr(addrB); ok {
netA.wg.Add(1)
netA.tryConnect(addrB, gossipB)
- // let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return len(netA.GetPeers(PeersConnectedOut)) == 1
+ }, time.Second, 50*time.Millisecond)
}
assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut)))
assert.Equal(t, 1, len(netB.GetPeers(PeersConnectedIn)))
@@ -1507,18 +1515,15 @@ func TestPeeringSenderIdentityChallengeOnly(t *testing.T) {
assert.Equal(t, 0, netA.identityTracker.(*mockIdentityTracker).getSetCount())
assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount())
- // bi-directional connection should also work
- if _, ok := netB.tryConnectReserveAddr(addrA); ok {
- netB.wg.Add(1)
- netB.tryConnect(addrA, gossipA)
- // let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
- }
- // the nodes are connected redundantly
- assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedIn)))
+ // bi-directional connection does not work because netA advertises its public address
+ _, ok = netB.tryConnectReserveAddr(addrA)
+ assert.False(t, ok)
+
+ // no redundant connections
+ assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut)))
assert.Equal(t, 1, len(netB.GetPeers(PeersConnectedIn)))
- assert.Equal(t, 1, len(netB.GetPeers(PeersConnectedOut)))
+ assert.Equal(t, 0, len(netB.GetPeers(PeersConnectedOut)))
// confirm identity map was not added to for either host
assert.Equal(t, 0, netA.identityTracker.(*mockIdentityTracker).getSetCount())
assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount())
@@ -1558,12 +1563,15 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) {
addrA = hostAndPort(addrA)
addrB = hostAndPort(addrB)
+ assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut)))
// first connection should work just fine
if _, ok := netA.tryConnectReserveAddr(addrB); ok {
netA.wg.Add(1)
netA.tryConnect(addrB, gossipB)
// let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return len(netA.GetPeers(PeersConnectedOut)) == 1
+ }, time.Second, 50*time.Millisecond)
}
// single A->B connection
assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
@@ -1580,7 +1588,9 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) {
netB.wg.Add(1)
netB.tryConnect(addrA, gossipA)
// let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return len(netB.GetPeers(PeersConnectedOut)) == 1
+ }, time.Second, 50*time.Millisecond)
}
assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedIn)))
assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut)))
@@ -1591,7 +1601,7 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) {
assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount())
}
-// TestPeeringIncorrectDeduplicationName confirm that if the reciever can't match
+// TestPeeringIncorrectDeduplicationName confirm that if the reciever can't match
// the Address in the challenge to its PublicAddress, identities aren't exchanged, but peering continues
func TestPeeringIncorrectDeduplicationName(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -1625,12 +1635,15 @@ func TestPeeringIncorrectDeduplicationName(t *testing.T) {
addrA = hostAndPort(addrA)
addrB = hostAndPort(addrB)
+ assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut)))
// first connection should work just fine
if _, ok := netA.tryConnectReserveAddr(addrB); ok {
netA.wg.Add(1)
netA.tryConnect(addrB, gossipB)
// let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
+ assert.Eventually(t, func() bool {
+ return len(netA.GetPeers(PeersConnectedOut)) == 1
+ }, time.Second, 50*time.Millisecond)
}
// single A->B connection
assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn)))
@@ -1643,14 +1656,18 @@ func TestPeeringIncorrectDeduplicationName(t *testing.T) {
assert.Equal(t, 0, netA.identityTracker.(*mockIdentityTracker).getSetCount())
assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount())
- // bi-directional connection should also work
+ // bi-directional connection would now work since netB detects to be connected to netA in tryConnectReserveAddr,
+ // so force it.
// this second connection should set identities, because the reciever address matches now
- if _, ok := netB.tryConnectReserveAddr(addrA); ok {
- netB.wg.Add(1)
- netB.tryConnect(addrA, gossipA)
- // let the tryConnect go forward
- time.Sleep(250 * time.Millisecond)
- }
+ _, ok = netB.tryConnectReserveAddr(addrA)
+ assert.False(t, ok)
+ netB.wg.Add(1)
+ netB.tryConnect(addrA, gossipA)
+ // let the tryConnect go forward
+ assert.Eventually(t, func() bool {
+ return len(netB.GetPeers(PeersConnectedOut)) == 1
+ }, time.Second, 50*time.Millisecond)
+
// confirm that at this point the identityTracker was called once per network
// and inserted once per network
assert.Equal(t, 1, netA.identityTracker.(*mockIdentityTracker).getSetCount())
@@ -1982,14 +1999,13 @@ func TestPeeringWithBadIdentityVerification(t *testing.T) {
partitiontest.PartitionTest(t)
type testCase struct {
- name string
- verifyResponse func(t *testing.T, h http.Header, c identityChallengeValue) (crypto.PublicKey, []byte, error)
- totalInA int
- totalOutA int
- totalInB int
- totalOutB int
- additionalSleep time.Duration
- occupied bool
+ name string
+ verifyResponse func(t *testing.T, h http.Header, c identityChallengeValue) (crypto.PublicKey, []byte, error)
+ totalInA int
+ totalOutA int
+ totalInB int
+ totalOutB int
+ occupied bool
}
testCases := []testCase{
@@ -2602,7 +2618,7 @@ func TestSlowPeerDisconnection(t *testing.T) {
peers, _ = netA.peerSnapshot(peers)
if len(peers) == 0 || peers[0] != peer {
// make sure it took more than 1 second, and less than 5 seconds.
- waitTime := time.Now().Sub(beforeLoopTime)
+ waitTime := time.Since(beforeLoopTime)
require.LessOrEqual(t, int64(time.Second), int64(waitTime))
require.GreaterOrEqual(t, int64(5*time.Second), int64(waitTime))
break
@@ -2895,7 +2911,7 @@ func TestWebsocketNetworkMessageOfInterest(t *testing.T) {
netB.config.EnablePingHandler = false
addrA, postListen := netA.Address()
require.True(t, postListen)
- t.Log(addrA)
+ t.Logf("netA %s", addrA)
netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole)
// have netB asking netA to send it ft2, deregister ping handler to make sure that we aren't exceeding the maximum MOI messagesize
@@ -2905,6 +2921,8 @@ func TestWebsocketNetworkMessageOfInterest(t *testing.T) {
netB.Start()
defer netStop(t, netB, "B")
+ addrB, _ := netB.Address()
+ t.Logf("netB %s", addrB)
incomingMsgSync := deadlock.Mutex{}
msgCounters := make(map[protocol.Tag]int)
@@ -3652,7 +3670,7 @@ func BenchmarkVariableTransactionMessageBlockSizes(t *testing.B) {
netB.Broadcast(context.Background(), protocol.TxnTag, dataBuffer, true, nil)
<-msgProcessed
}
- deltaTime := time.Now().Sub(startTime)
+ deltaTime := time.Since(startTime)
rate = float64(t.N) * float64(time.Second) / float64(deltaTime)
t.ReportMetric(rate, "txn/sec")
})
@@ -3794,7 +3812,6 @@ func TestWebsocketNetworkTelemetryTCP(t *testing.T) {
type mockServer struct {
*httptest.Server
URL string
- t *testing.T
waitForClientClose bool
}
@@ -3845,7 +3862,7 @@ func (t mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
wr.Close()
- for true {
+ for {
// echo a message back to the client
_, _, err := ws.NextReader()
if err != nil {
@@ -4002,7 +4019,7 @@ func TestDiscardUnrequestedBlockResponse(t *testing.T) {
require.Eventually(t, func() bool { return netA.NumPeers() == 1 }, 500*time.Millisecond, 25*time.Millisecond)
// send an unrequested block response
- msg := make([]sendMessage, 1, 1)
+ msg := make([]sendMessage, 1)
msg[0] = sendMessage{
data: append([]byte(protocol.TopicMsgRespTag), []byte("foo")...),
enqueued: time.Now(),
diff --git a/network/wsPeer.go b/network/wsPeer.go
index 55dba8e56..7cbdbeaeb 100644
--- a/network/wsPeer.go
+++ b/network/wsPeer.go
@@ -360,6 +360,7 @@ func makePeerCore(ctx context.Context, net GossipNode, log logging.Logger, readB
}
// GetAddress returns the root url to use to connect to this peer.
+// This implements HTTPPeer interface and used by external services to determine where to connect to.
// TODO: should GetAddress be added to Peer interface?
func (wp *wsPeerCore) GetAddress() string {
return wp.rootURL
@@ -404,7 +405,16 @@ func (wp *wsPeer) RoutingAddr() []byte {
return true
}
- ip := wp.IPAddr()
+ var ip []byte
+ // originAddress is set for incoming connections
+ // and optionally includes reverse proxy support.
+ // see RequestTracker.getForwardedConnectionAddress for details.
+ if wp.wsPeerCore.originAddress != "" {
+ ip = net.ParseIP(wp.wsPeerCore.originAddress)
+ } else {
+ ip = wp.IPAddr()
+ }
+
if len(ip) != net.IPv6len {
return ip
}
diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go
index 59217047c..91cd3cb37 100644
--- a/network/wsPeer_test.go
+++ b/network/wsPeer_test.go
@@ -310,4 +310,10 @@ func TestWsPeerIPAddr(t *testing.T) {
require.Equal(t, 16, len(conn.addr.IP))
require.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 4}, peer.IPAddr())
require.Equal(t, []byte{127, 0, 0, 4}, peer.RoutingAddr())
+
+ // check incoming peer with originAddress set
+ conn.addr.IP = []byte{127, 0, 0, 1}
+ peer.wsPeerCore.originAddress = "127.0.0.2"
+ require.Equal(t, []byte{127, 0, 0, 1}, peer.IPAddr())
+ require.Equal(t, []byte{127, 0, 0, 2}, peer.RoutingAddr())
}
diff --git a/package-deploy.yaml b/package-deploy.yaml
index 871d6ef1f..8daf262ac 100644
--- a/package-deploy.yaml
+++ b/package-deploy.yaml
@@ -30,8 +30,6 @@ agents:
dockerFilePath: docker/build/aptly.Dockerfile
image: algorand/aptly
version: scripts/configure_dev-deps.sh
- buildArgs:
- - GOLANG_VERSION=`./scripts/get_golang_version.sh`
env:
- AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
@@ -42,6 +40,8 @@ agents:
volumes:
- $XDG_RUNTIME_DIR/gnupg/S.gpg-agent:/root/.gnupg/S.gpg-agent
- $HOME/.gnupg/pubring.kbx:/root/.gnupg/pubring.kbx
+ - $HOME/.gnupg/secring.gpg:/root/.gnupg/secring.gpg
+ - $HOME/.aptly:/root/.aptly
workDir: $HOME/projects/go-algorand
- name: rpm
@@ -64,7 +64,6 @@ agents:
- $HOME/.gnupg/pubring.kbx:/root/.gnupg/pubring.kbx
workDir: $HOME/projects/go-algorand
-
tasks:
- task: docker.Make
name: docker
diff --git a/protocol/consensus.go b/protocol/consensus.go
index b32e24566..928ba4b6f 100644
--- a/protocol/consensus.go
+++ b/protocol/consensus.go
@@ -217,6 +217,12 @@ const ConsensusV38 = ConsensusVersion(
"https://github.com/algorandfoundation/specs/tree/abd3d4823c6f77349fc04c3af7b1e99fe4df699f",
)
+// ConsensusV39 enables dynamic filter timeouts, a deadline timeout of 4 seconds,
+// TEAL v10 logicSig opcode budget pooling along with elliptic curve ops on some pairing friendly curves.
+const ConsensusV39 = ConsensusVersion(
+ "https://github.com/algorandfoundation/specs/tree/925a46433742afb0b51bb939354bd907fa88bf95",
+)
+
// ConsensusFuture is a protocol that should not appear in any production
// network, but is used to test features before they are released.
const ConsensusFuture = ConsensusVersion(
@@ -246,7 +252,7 @@ const ConsensusVAlpha5 = ConsensusVersion("alpha5")
// ConsensusCurrentVersion is the latest version and should be used
// when a specific version is not provided.
-const ConsensusCurrentVersion = ConsensusV38
+const ConsensusCurrentVersion = ConsensusV39
// Error is used to indicate that an unsupported protocol has been detected.
type Error ConsensusVersion
diff --git a/scripts/release/mule/deploy/deb/deploy.sh b/scripts/release/mule/deploy/deb/deploy.sh
index 395aa7060..c9c4b4b6c 100755
--- a/scripts/release/mule/deploy/deb/deploy.sh
+++ b/scripts/release/mule/deploy/deb/deploy.sh
@@ -32,34 +32,7 @@ aptly mirror update beta
aptly repo import stable stable algorand algorand-devtools
aptly repo import beta beta algorand-beta algorand-devtools-beta
-KEY_PREFIX="$CHANNEL/$VERSION"
-FILENAME_SUFFIX="${CHANNEL}_linux-amd64_${VERSION}.deb"
-ALGORAND_KEY="$KEY_PREFIX/algorand_${FILENAME_SUFFIX}"
-DEVTOOLS_KEY="$KEY_PREFIX/algorand-devtools_${FILENAME_SUFFIX}"
-
-# `STAGING` could contain a "path" (i.e. "my_bucket/foo/bar"), but the
-# `s3api` api expects it to be only the bucket name (i.e., "my_bucket").
-BUCKET=$(awk -F/ '{ print $1 }' <<< "$STAGING")
-
-# If the strings match then the objects are in the top-level of the bucket.
-if [ "$STAGING" = "$BUCKET" ]
-then
- BUCKET_PREFIX_PATH="$STAGING"
-else
- # Remove matching prefix.
- BUCKET_PREFIX_PATH=${STAGING#$BUCKET"/"}
-fi
-
-for key in {"$ALGORAND_KEY","$DEVTOOLS_KEY"}
-do
- key="$BUCKET_PREFIX_PATH/$key"
- if aws s3api head-object --bucket "$BUCKET" --key "$key"
- then
- aws s3 cp "s3://$BUCKET/$key" "$PACKAGES_DIR"
- else
- echo "[$0] The package \`$key\` failed to download."
- fi
-done
+cp -f tmp/{algorand,algorand-devtools}_${CHANNEL}_linux-{amd64,arm64}_${VERSION}.deb $PACKAGES_DIR
if ls -A $PACKAGES_DIR
then
@@ -68,7 +41,7 @@ then
aptly snapshot create "$SNAPSHOT" from repo "$CHANNEL"
if ! aptly publish show "$CHANNEL" s3:algorand-releases: &> /dev/null
then
- aptly publish snapshot -gpg-key=dev@algorand.com -origin=Algorand -label=Algorand "$SNAPSHOT" s3:algorand-releases:
+ aptly publish -batch snapshot -gpg-key=dev@algorand.com -origin=Algorand -label=Algorand "$SNAPSHOT" s3:algorand-releases:
else
aptly publish switch "$CHANNEL" s3:algorand-releases: "$SNAPSHOT"
fi
@@ -76,4 +49,3 @@ else
echo "[$0] The packages directory is empty, so there is nothing to add the \`$CHANNEL\` repo."
exit 1
fi
-
diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go
index 4631ecd6e..287553acf 100644
--- a/test/e2e-go/features/stateproofs/stateproofs_test.go
+++ b/test/e2e-go/features/stateproofs/stateproofs_test.go
@@ -843,7 +843,11 @@ func TestTotalWeightChanges(t *testing.T) {
richNode.goOffline(a, &fixture, rnd)
}
- a.NoError(fixture.WaitForRound(rnd, 30*time.Second))
+ if testing.Short() {
+ a.NoError(fixture.WaitForRound(rnd, 30*time.Second))
+ } else {
+ a.NoError(fixture.WaitForRound(rnd, 60*time.Second))
+ }
blk, err := libgoal.BookkeepingBlock(rnd)
a.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd)
diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go
index 1cc0b24fb..3b54f9f7a 100644
--- a/test/framework/fixtures/libgoalFixture.go
+++ b/test/framework/fixtures/libgoalFixture.go
@@ -119,6 +119,10 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e
if f.t == nil {
return
}
+
+ f.t.Logf("Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err)
+ f.dumpLogs(filepath.Join(nc.GetDataDir(), "node.log"))
+
exitError, ok := err.(*exec.ExitError)
if !ok {
require.NoError(f.t, err, "Node at %s has terminated with an error", nc.GetDataDir())