diff --git a/.aptly.conf b/.aptly.conf index 6300abc3b7..8badff78da 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 0e0d5f19c4..ef2dd76c2b 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 36348615b5..8b02bf082d 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 21d65ecf88..4e7ec685e3 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 bd2c2c84da..2c598ff779 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 2bb2ffe818..a60c9f44c4 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 6ab144470f..86b9a18774 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 f6ff27fd2b..0a7ef55f96 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 664fd3a2b0..000f03e1ea 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 d00491fd7e..573541ac97 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -1 +0 diff --git a/catchup/service.go b/catchup/service.go index bcf204b134..01ff1678ef 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 fc0ae38e1d..c712fab5a3 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 64dfe7329d..fc7655173f 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 a2f28b97d5..a1baa92044 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 61c2381fa0..25c6edab05 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 17bd00a9b5..f7e656d40d 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 b37f2001c4..e327e0e3be 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 c4fb9394cf..be9237b90e 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 95a1dbcce1..938d3ee102 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 ef0de80850..acf5fc94b7 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 1ffe766db6..4a3438b8ff 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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", - "NHly8mjy8f8HAAD//3CL32ln9wAA", + "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", + "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 3fdcd13419..e7e56520a4 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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", } // 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 c30caa6c94..b9fc3b1baa 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 0e0d78aa42..b866cc9f42 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 6d7d2c4177..713e5cd7b0 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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", - "How+/t8AAAD//0RubYkspwEA", + "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", + "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 f352c24c7e..c0e0c71fe2 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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", } // 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 107ad4eba1..0938833520 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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", - "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 bc7e71527c..b82212a19d 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 2b4fa8c81f..a4cc3d6382 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 3ba9037fc1..a38ba1462e 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 e98d6c2441..31f8fb05be 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 4ec00a52bb..81d742fbd4 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 → ..., 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 → ... +- 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 → ... +- 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 b8efb37fa3..71d756a1dc 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 → ..., 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 54f053686f..f2ff330591 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 → ..., 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 19c75d201a..c45c0af77d 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 ad371c6a1f..4938f95aa9 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 5f08878a93..6649627440 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 eda022e721..e11b41b8a2 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 c060d82623..bfecb01a67 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 2db51b24a7..836b8cb852 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 06fee09c13..4682732fb5 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 436643e4c2..d25851be0d 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 fd8b9ff806..05e5786013 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 bb5c2179e9..060516d65f 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 7d3c2b42f2..37bfeb9bcc 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 f4736f6214..2fd432956a 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 b659e662af..6839e40926 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 7ee54fb98d..d971b27154 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": [ @@ -4644,6 +4646,39 @@ "State Access" ] }, + { + "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", diff --git a/data/transactions/logic/langspec_v2.json b/data/transactions/logic/langspec_v2.json index b518b1a42f..a832f86643 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 a8bc6df0da..f1566b5289 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 ffc428822b..49591c10f6 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 2b946629cc..ccde5509fc 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 20575dae5a..72e1d9c64f 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 d8be33960c..7229ee534c 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 3b496ddcb8..2f1576864a 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 c52d36862d..3ec2d39af7 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 a111eec131..a575c493dd 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 37f8ba790f..e67907ea4d 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 13a6d92d7a..f40faf1be1 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 4b4f965a65..3643ade21f 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 dc2d44bc09..1f595dfb7c 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 56cf7b5dc8..e414922e81 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 5894409ba9..37603c7944 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 5d44cbe2f1..323447464b 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 416c0e4c08..1e7793248b 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 1849d8e5e1..24982e7412 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 5091afefa9..e82dba3d71 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 0000000000..6609d5f4bc --- /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 --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 **** 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/ +GET /v2/participation/ +GET /v2/participation/ +``` + +## 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 2c46d02c09..a4c8d22c67 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 d797a4a560..f3e6b3d049 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 225b037997..5e1253adce 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 58f7fc8a4f..714e6bddf4 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 b916558314..cb6bf08784 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 3fbe61512c..7d2bf83809 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 a53d2f8ec3..5c9d2ad8e4 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 afc6a2124e..99ac9b416e 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 2bc600ee38..e590250c94 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 019b502717..97693bbe2b 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 26e0420946..f1a9c179e9 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 025d75a5a6..63fd4a72b6 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 8998b41f77..65349987ea 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 0000000000..e0888c5449 --- /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 . + +// 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 7f8b3046c9..d316fcd814 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 05e484843a..f8eeeb71f4 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 55dba8e568..7cbdbeaebc 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 59217047ce..91cd3cb37d 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 871d6ef1f7..8daf262acc 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 b32e245662..928ba4b6f0 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 395aa7060a..c9c4b4b6c4 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 4631ecd6e9..287553acf9 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 1cc0b24fb8..3b54f9f7aa 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())